← Kursa Dön
📄 Text · 35 min

Volume Backup ve Migration Stratejileri

Bu bölümün son dersine geldik. Şimdiye kadar volume'ları oluşturmayı, named volume ile veritabanı verilerini korumayı, bind mount ile geliştirme ortamı kurmayı öğrendik. Ama bir soru kaldı: volume'daki veriyi nasıl yedeklersin?

Şöyle düşün: bir fotoğraf stüdyon var. Müşterilerinin düğün fotoğrafları — kaybolursa geri dönüşü yok. Bu yüzden üç kural var: (1) Her gün yedekle, (2) Yedeği farklı bir yerde tut, (3) Yedeğin geri yüklendiğini test et. Yani sadece "yedek alıyorum" demek yetmez — hiç geri yükleme testi yapmadıysan, yedeğin çalışıp çalışmadığını bilmiyorsun demektir.

Docker volume'ları da aynı mantıkta. Container silinse bile volume kalıyor — bu güzel. Ama ya disk bozulursa? Ya yanlışlıkla docker volume prune çalıştırırsan? Backup stratejisi olmadan, her şeyi kaybedebilirsin.


Temel Backup Yöntemi — Tar ile Yedekleme

Docker volume'unu yedeklemenin temel mantığı basit: geçici bir container oluştur, volume'u bağla, içeriğini tar dosyasına sıkıştır, host'a kaydet.

Hadi adım adım yapalım:

docker run --rm \
    -v pgdata:/source:ro \
    -v $(pwd)/backups:/backup \
    alpine \
    tar czf /backup/pgdata-$(date +%Y%m%d-%H%M%S).tar.gz -C /source .

Bu tek komut çok şey yapıyor, açıklayayım:

  1. --rm — Container işi bitince kendini siler, çöp bırakmaz

  2. -v pgdata:/source:ro — Yedekleyeceğimiz volume'u /source'a read-only bağlıyoruz

  3. -v $(pwd)/backups:/backup — Host'taki backups dizinini /backup'a bağlıyoruz

  4. alpine — Küçük ve hızlı bir image, sadece tar komutu çalıştırmak için

  5. tar czf .../source'un tüm içeriğini sıkıştırıp /backup'a yazıyor

Sonuçta backups/ dizininde pgdata-20250227-220000.tar.gz gibi bir dosya oluşuyor. Backup'ın boyutunu ve içeriğini kontrol edelim:

ls -lh backups/
# -rw-r--r--  1 root root  45M Feb 27 22:00 pgdata-20250227-220000.tar.gz

# İçeriğini listele (açmadan)
tar tzf backups/pgdata-20250227-220000.tar.gz | head -10

Restore — Geri Yükleme

Backup'tan geri yüklemek de benzer şekilde çalışır:

# Yeni volume oluştur
docker volume create pgdata-restored

# Backup'tan geri yükle
docker run --rm \
    -v pgdata-restored:/target \
    -v $(pwd)/backups:/backup:ro \
    alpine \
    sh -c "cd /target && tar xzf /backup/pgdata-20250227-220000.tar.gz"

# Yeni volume ile container başlat
docker run -d \
    --name postgres-restored \
    -v pgdata-restored:/var/lib/postgresql/data \
    -e POSTGRES_PASSWORD=secret \
    postgres:16

# Veriyi doğrula
docker exec postgres-restored psql -U postgres -c "SELECT count(*) FROM users;"

Veriler geri geldi! Ama burada kritik bir uyarı var: çalışan bir veritabanının volume'unu tar ile yedeklemek tehlikelidir. Neden? Çünkü veritabanı dosyalara yazarken tar'lıyorsun — yarım yazılmış dosyalar backup'a girebilir ve tutarsız veri oluşur. Bu yüzden veritabanları için veritabanının kendi backup aracını kullanman gerekiyor.


Veritabanı Bazlı Backup Stratejileri

Her veritabanının kendi backup aracı var ve bunlar "tutarlı snapshot" almayı garanti eder. Bunları kullanmak, tar ile volume backup'tan çok daha güvenli.

PostgreSQL — pg_dump

PostgreSQL'in pg_dump aracı, veritabanı çalışırken bile tutarlı bir backup alabilir. Çünkü MVCC (Multi-Version Concurrency Control) kullanır — backup alırken okuma ve yazma işlemleri devam eder:

# Tek veritabanı — SQL format
docker exec postgres pg_dump -U admin -d myapp > backup.sql

# Tek veritabanı — Custom format (önerilen, daha küçük, paralel restore destekler)
docker exec postgres pg_dump -U admin -d myapp -Fc > backup.dump

# Tüm veritabanları
docker exec postgres pg_dumpall -U admin > all-databases.sql

# Sadece belirli tablolar
docker exec postgres pg_dump -U admin -d myapp -t users -t orders > tables.sql

# Sadece şema (veri olmadan)
docker exec postgres pg_dump -U admin -d myapp --schema-only > schema.sql

Restore etmek de kolay:

# SQL dump'tan
docker exec -i postgres psql -U admin -d myapp < backup.sql

# Custom format'tan (paralel restore — çok daha hızlı!)
docker exec -i postgres pg_restore -U admin -d myapp -j 4 < backup.dump

-j 4 parametresi 4 paralel iş parçacığı kullanarak restore yapar. Büyük veritabanlarında bu ciddi hız farkı yaratır.

MySQL — mysqldump

# Tek veritabanı
docker exec mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} \
    --single-transaction myapp > backup.sql

# Tüm veritabanları
docker exec mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} \
    --all-databases --single-transaction > all-dbs.sql

# Sıkıştırılmış
docker exec mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} \
    --single-transaction myapp | gzip > backup.sql.gz

--single-transaction InnoDB tablolarında tutarlı snapshot alır — backup sırasında tabloları kilitlemez, uygulama normal çalışmaya devam eder.

Redis — Snapshot

# RDB snapshot tetikle
docker exec redis redis-cli BGSAVE
# Background saving started

# RDB dosyasını kopyala
docker cp redis:/data/dump.rdb ./redis-backup-$(date +%Y%m%d).rdb

Redis zaten belirli aralıklarla otomatik RDB snapshot'ı alır. BGSAVE ile anlık bir snapshot da tetikleyebilirsin.

MongoDB — mongodump

docker exec mongo mongodump \
    --uri="mongodb://admin:secret@localhost:27017" \
    --out=/tmp/backup --gzip

docker cp mongo:/tmp/backup ./mongo-backup-$(date +%Y%m%d)

Otomatik Backup Script'i

Manuel backup almak bir yerden sonra unutulur. Otomatize etmek şart. İşte production'da kullanabileceğin bir backup script'i:

#!/bin/bash
# volume-backup.sh — Docker volume'larını otomatik yedekler
set -euo pipefail

BACKUP_DIR="/backups/docker-volumes"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d-%H%M%S)
LOG_FILE="/var/log/docker-volume-backup.log"

VOLUMES=("pgdata" "redis-data" "app-uploads")

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

backup_volume() {
    local volume=$1
    local backup_file="${BACKUP_DIR}/${volume}-${DATE}.tar.gz"

    log "Backup başlıyor: ${volume}"

    if ! docker volume inspect "$volume" &>/dev/null; then
        log "UYARI: Volume '${volume}' bulunamadı, atlıyorum"
        return 1
    fi

    docker run --rm \
        -v "${volume}:/source:ro" \
        -v "${BACKUP_DIR}:/backup" \
        alpine \
        tar czf "/backup/${volume}-${DATE}.tar.gz" -C /source .

    local size=$(du -sh "$backup_file" | cut -f1)
    log "Tamamlandı: ${volume} → ${backup_file} (${size})"

    # Eski backup'ları temizle
    local deleted=$(find "${BACKUP_DIR}" -name "${volume}-*.tar.gz" \
        -mtime +${RETENTION_DAYS} -delete -print | wc -l)
    [ "$deleted" -gt 0 ] && log "${deleted} eski backup silindi (${volume})"
}

mkdir -p "$BACKUP_DIR"
log "========== Backup Başladı =========="

success=0; fail=0
for volume in "${VOLUMES[@]}"; do
    if backup_volume "$volume"; then ((success++)); else ((fail++)); fi
done

log "========== Backup Tamamlandı: ${success} başarılı, ${fail} hatalı =========="
[ "$fail" -eq 0 ] || exit 1

Bu script'i cron ile her gece otomatik çalıştırabilirsin:

chmod +x volume-backup.sh

# Her gece 02:00'de çalıştır
crontab -e
# 0 2 * * * /path/to/volume-backup.sh

PostgreSQL Özel Backup Script'i

Veritabanları için tar yerine veritabanının kendi aracını kullanan bir script daha yazalım:

#!/bin/bash
# pg-backup.sh
CONTAINER="postgres"
DB_USER="admin"
BACKUP_DIR="/backups/postgresql"
DATE=$(date +%Y%m%d-%H%M%S)

mkdir -p "$BACKUP_DIR"

# Custom format dump (en verimli)
docker exec "$CONTAINER" pg_dump -U "$DB_USER" -d myapp -Fc --compress=9 \
    > "${BACKUP_DIR}/myapp-${DATE}.dump"

SIZE=$(du -sh "${BACKUP_DIR}/myapp-${DATE}.dump" | cut -f1)
echo "[$(date)] Backup: myapp-${DATE}.dump (${SIZE})"

# Bütünlük kontrolü
docker exec -i "$CONTAINER" pg_restore --list \
    < "${BACKUP_DIR}/myapp-${DATE}.dump" > /dev/null 2>&1

if [ $? -eq 0 ]; then
    echo "[$(date)] Bütünlük kontrolü: BAŞARILI ✓"
else
    echo "[$(date)] Bütünlük kontrolü: BAŞARISIZ ✗ — backup bozuk olabilir!"
    exit 1
fi

# 14 günden eski backup'ları temizle
find "$BACKUP_DIR" -name "myapp-*.dump" -mtime +14 -delete

Dikkat et, backup aldıktan sonra pg_restore --list ile bütünlük kontrolü yapıyoruz. Bu, backup dosyasının bozuk olup olmadığını doğrular. Production'da bu adımı asla atlama.


Hot Backup vs Cold Backup

İki farklı backup yaklaşımı var ve hangisini ne zaman kullanacağını bilmen gerekiyor.

Cold Backup — Container durdurularak alınır. Veri tutarsızlığı riski sıfır ama downtime var:

echo "Container'lar durduruluyor..."
docker compose stop

echo "Backup alınıyor..."
docker run --rm \
    -v pgdata:/source:ro \
    -v $(pwd)/backups:/backup \
    alpine tar czf /backup/pgdata-cold-$(date +%Y%m%d).tar.gz -C /source .

echo "Container'lar başlatılıyor..."
docker compose start

1GB'lık bir volume için downtime yaklaşık 30-60 saniye. Küçük projeler için kabul edilebilir.

Hot Backup — Container çalışırken alınır. Downtime yok ama veritabanının kendi aracını kullanman şart:

# PostgreSQL — çalışırken güvenli backup
docker exec postgres pg_dump -U admin -Fc myapp > backup.dump

# MySQL — çalışırken güvenli backup
docker exec mysql mysqldump --single-transaction -u root -p$PASS myapp > backup.sql

Bu araçlar veritabanının iç mekanizmalarını kullanarak tutarlı snapshot alır — okuma/yazma işlemleri devam ederken.

⚠️ Çok önemli: Çalışan bir veritabanının volume'unu tar ile backup almak tehlikelidir! Veritabanı dosyalara yazarken tar'lıyorsun — tutarsız veri oluşabilir. Her zaman veritabanının kendi backup aracını kullan.


Volume Migration — Sunucular Arası Taşıma

Sunucu değişikliği, cloud migration veya disaster recovery için volume'ları bir host'tan diğerine taşıman gerekebilir.

Yöntem 1 — Tar + SCP (Basit)

# Kaynak host'ta backup al
docker run --rm -v pgdata:/source:ro -v $(pwd):/backup \
    alpine tar czf /backup/pgdata-migration.tar.gz -C /source .

# Hedef host'a kopyala
scp pgdata-migration.tar.gz user@new-host:/tmp/

# Hedef host'ta restore et
ssh user@new-host
docker volume create pgdata
docker run --rm -v pgdata:/target -v /tmp:/backup:ro \
    alpine sh -c "cd /target && tar xzf /backup/pgdata-migration.tar.gz"

Yöntem 2 — SSH Pipe (Ara Dosya Olmadan)

# Tek komutla doğrudan aktarım
docker run --rm -v pgdata:/source:ro alpine tar czf - -C /source . | \
    ssh user@new-host \
    'docker run --rm -i -v pgdata:/target alpine sh -c "cd /target && tar xzf -"'

Bu komut backup'ı dosyaya yazmadan doğrudan SSH üzerinden hedef host'a aktarır. Disk alanı kısıtlı olduğunda çok kullanışlı.

Yöntem 3 — Veritabanı Native Araçlarla

Veritabanları için en güvenilir yöntem, veritabanının kendi dump/restore aracını kullanmak:

# Kaynak host'ta dump al
docker exec postgres pg_dump -U admin -d myapp -Fc > myapp.dump
scp myapp.dump user@new-host:/tmp/

# Hedef host'ta restore et
docker exec -i postgres-new pg_restore -U admin -d myapp -j 4 < /tmp/myapp.dump

Backup Doğrulama — Test Edilmemiş Backup, Backup Değildir

Bu cümleyi kafana kazı: test edilmemiş backup, backup değildir. Altı ay boyunca her gece backup aldın, sonra gerçekten lazım olduğunda backup bozuk çıkabilir. Düzenli restore testi yapmak şart.

İşte otomatik doğrulama script'i:

#!/bin/bash
# backup-verify.sh
BACKUP_FILE=$1
TEST_VOLUME="test-restore-$$"

echo "=== Backup Doğrulama ==="
echo "Dosya: ${BACKUP_FILE}"

# Test volume oluştur
docker volume create "$TEST_VOLUME"

# Restore et
docker run --rm \
    -v "${TEST_VOLUME}:/target" \
    -v "$(pwd):/backup:ro" \
    alpine sh -c "cd /target && tar xzf /backup/${BACKUP_FILE}"

if [ $? -ne 0 ]; then
    echo "❌ RESTORE BAŞARISIZ"
    docker volume rm "$TEST_VOLUME"
    exit 1
fi

# Dosya sayısı kontrolü
FILE_COUNT=$(docker run --rm -v "${TEST_VOLUME}:/data" alpine find /data -type f | wc -l)
TOTAL_SIZE=$(docker run --rm -v "${TEST_VOLUME}:/data" alpine du -sh /data | cut -f1)
echo "Dosyalar: ${FILE_COUNT}, Boyut: ${TOTAL_SIZE}"

if [ "$FILE_COUNT" -eq 0 ]; then
    echo "❌ BOŞ BACKUP!"
    docker volume rm "$TEST_VOLUME"
    exit 1
fi

# Temizlik
docker volume rm "$TEST_VOLUME"
echo "✅ BACKUP DOĞRULANDI"

Bu script'i her backup'tan sonra veya en azından haftada bir çalıştır.


3-2-1 Backup Kuralı

Veri koruma dünyasının altın standardı 3-2-1 kuralıdır:

  • 3 kopya: Orijinal veri + 2 yedek

  • 2 farklı medya: Yerel disk + uzak depolama (S3, NFS, farklı sunucu)

  • 1 offsite: En az bir yedek fiziksel olarak farklı lokasyonda

S3'e Yükleme

#!/bin/bash
# backup-to-s3.sh
VOLUME=$1
BUCKET="s3://my-backups/docker-volumes"
DATE=$(date +%Y%m%d-%H%M%S)
TEMP="/tmp/${VOLUME}-${DATE}.tar.gz"

# Yerel backup
docker run --rm -v "${VOLUME}:/source:ro" -v /tmp:/backup \
    alpine tar czf "/backup/${VOLUME}-${DATE}.tar.gz" -C /source .

# S3'e yükle
aws s3 cp "$TEMP" "${BUCKET}/${VOLUME}/" --storage-class STANDARD_IA

# Temp dosyayı sil
rm -f "$TEMP"
echo "Yüklendi: ${BUCKET}/${VOLUME}/"

Docker Compose ile Backup Servisi

Backup'ı Compose projesine entegre edebilirsin:

services:
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data

  backup:
    image: alpine:3.19
    volumes:
      - pgdata:/source/pgdata:ro
      - ./backups:/backup
    command: |
      sh -c '
      DATE=$$(date +%Y%m%d-%H%M%S)
      tar czf /backup/pgdata-$$DATE.tar.gz -C /source/pgdata .
      find /backup -name "*.tar.gz" -mtime +7 -delete
      echo "Backup tamamlandı: $$DATE"
      '
    profiles:
      - backup

volumes:
  pgdata:
# Manuel backup
docker compose --profile backup run --rm backup

# Cron ile otomatik
# 0 3 * * * cd /path/to/project && docker compose --profile backup run --rm backup

profiles: [backup] sayesinde bu servis normal docker compose up'ta çalışmaz — sadece --profile backup ile çağırıldığında çalışır.


Disaster Recovery Planı

Her production ortamının bir disaster recovery planı olmalı. İşte basit ama etkili bir plan:

#!/bin/bash
# disaster-recovery.sh — Tam ortam geri yükleme
set -euo pipefail

BACKUP_DIR=$1
COMPOSE_FILE=$2

echo "=== DISASTER RECOVERY ==="

# Volume'ları restore et
for archive in "${BACKUP_DIR}"/*.tar.gz; do
    VOLUME_NAME=$(basename "$archive" .tar.gz | sed 's/-[0-9]*-[0-9]*$//')
    echo "Restoring: ${VOLUME_NAME}"
    docker volume create "$VOLUME_NAME" 2>/dev/null || true
    docker run --rm \
        -v "${VOLUME_NAME}:/target" \
        -v "${BACKUP_DIR}:/backup:ro" \
        alpine sh -c "cd /target && tar xzf /backup/$(basename $archive)"
    echo "  ✓ ${VOLUME_NAME}"
done

# Servisleri başlat
docker compose -f "$COMPOSE_FILE" up -d

# Sağlık kontrolü
sleep 30
docker compose -f "$COMPOSE_FILE" ps
echo "=== RECOVERY TAMAMLANDI ==="

Bu script'i düzenli olarak test et. Yedek bir sunucuda çalıştırıp her şeyin doğru geldiğini doğrula. Gerçek bir felaket anında "acaba çalışır mı?" diye düşünmek istemezsin.


Bu Derste Ne Öğrendik?

Bu derste veri koruma ve taşıma stratejilerini öğrendik:

  • Temel backup yöntemi: geçici container + tar sıkıştırma + host'a kaydet.

  • Veritabanları için veritabanına özgü araçlar (pg_dump, mysqldump) kullan — tar ile değil.

  • Hot backup veritabanı araçlarıyla güvenli; cold backup her zaman güvenli.

  • Backup'ları otomatize et (cron, compose profile) ve restore testi yap.

  • 3-2-1 kuralı: 3 kopya, 2 farklı medya, 1 offsite.

  • Migration için tar + scp, SSH pipe veya veritabanı native araçları kullan.

  • Test edilmemiş backup, backup değildir.

Bu, volumes bölümünün son dersiydi. Artık Docker'da veri yönetiminin tüm yönlerini biliyorsun. Bir sonraki bölümde Docker Compose'a geçeceğiz — birden fazla container'ı tek dosyada tanımlayıp tek komutla yönetmeyi öğreneceğiz.