Docker volume yedeklemenin belgelerde önerilen yolu, volume'u geçici bir container'a bağlamak, host'taki bir dizini de aynı container'a bağlamak ve volume içeriğini o dizine bir tar arşivi olarak yazmaktır. Geri yükleme bunun tersidir: arşivi yeni ya da boş bir volume'a bağlı başka bir geçici container içinde açarsınız. Aynı iki adım, volume'u bir sunucudan diğerine taşımanın da temelidir; arada yalnızca arşiv dosyasını yeni sunucuya kopyalarsınız.

Bu yazıda yedekleme, geri yükleme ve taşıma adımlarını ayrıntılı olarak açıklıyor, ardından birden fazla makinenin aynı veriye erişmesi gerektiğinde kullanılan volume sürücülerini (NFS, CIFS/Samba, eklenti tabanlı sürücüler ve blok aygıtlar) ele alıyoruz. Volume oluşturma, --mount sözdizimi ve boş volume davranışı gibi temel konular Docker volume kullanımı yazısında anlatılıyor; burada onları bildiğinizi varsayıyoruz.

Volume dizinini neden doğrudan kopyalamamalısınız?

Volume dizinini neden doğrudan kopyalamamalısınız?

Volume verisi Linux'ta genellikle /var/lib/docker/volumes/<ad>/_data altında durduğu için ilk akla gelen, bu dizini cp ya da rsync ile kopyalamaktır. Docker belgeleri ise volume verisine container dışından doğrudan erişmeyi desteklenmeyen ve tanımsız bir davranış olarak tanımlıyor ve bunun volume'u ya da veriyi beklenmedik biçimde bozabileceğini söylüyor. Ayrıca /var/lib/docker altındaki dosya ve dizinleri doğrudan değiştirmemeniz gerektiği, storage driver sayfalarında da ayrıca vurgulanan bir uyarıdır.

Bir başka sebep de verinin yerinin sabit olmamasıdır. Daemon'ın veri dizini data-root ayarıyla değiştirilebilir ve containerd image store kullanıldığında imaj ve container verisi farklı bir dizinde tutulurken volume'lar /var/lib/docker altında kalır. Yedek betiğinizi belirli bir host yoluna bağlarsanız bu ayarlardan biri değiştiğinde betik sessizce boş ya da yanlış bir dizini yedekleyebilir. Volume'u bir container'a bağlayarak yedeklemek, verinin host üzerinde nerede durduğundan bağımsızdır.

Volume yedekleme: geçici container ve tar

Belgelerdeki örnek, önce /dbdata dizininde anonim bir volume kullanan dbstore adlı bir container oluşturur:

bash
docker run -v /dbdata --name dbstore ubuntu /bin/bash

Ardından yedeği alan asıl komut gelir. Bu tek satırda üç şey olur: --volumes-from dbstore yeni container'a dbstore'un volume'larını bağlar; -v $(pwd):/backup host'taki o anki dizini container içinde /backup olarak bağlar; tar cvf /backup/backup.tar /dbdata de volume içeriğini bu dizine arşivler. --rm sayesinde iş bitince geçici container silinir.

bash
docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

Komut tamamlanıp container durduğunda host'taki dizinde backup.tar adında, dbdata volume'unun yedeği olan bir dosya oluşur. Bu yöntemde yedeği alan araç (burada ubuntu imajındaki tar) ile veriyi kullanan uygulama birbirinden ayrıdır; uygulamanın imajında tar olmasa bile yedek alabilirsiniz.

Pratikte veriniz büyük ihtimalle adlandırılmış bir volume'dadır. Aynı mantığı --volumes-from yerine volume adını doğrudan bağlayarak da kurabilirsiniz. Aşağıdaki uyarlamada db-data volume'u salt okunur bağlanır; böylece yedek alırken volume'a yanlışlıkla yazılmaz:

bash
docker run --rm \
  -v db-data:/dbdata:ro \
  -v "$(pwd)":/backup \
  ubuntu tar cvf /backup/db-data.tar /dbdata

tar dosyaları dosya düzeyinde okur. Volume'u kullanan uygulama yedek sırasında yazmaya devam ederse arşivdeki dosyalar farklı anlarda okunmuş olur. Belgeler bu yöntemi tutarlılık açısından ayrıca tartışmıyor; bu yüzden yazma yoğun bir uygulamanın verisini yedeklerken uygulamayı kısa süreliğine durdurmak ya da uygulamanın kendi dışa aktarma aracını kullanmak, kendi değerlendirmeniz gereken bir karardır.

Yedekten geri yükleme

Yedeği aynı container'a ya da başka bir yerde oluşturduğunuz yeni bir container'a geri yükleyebilirsiniz. Belgelerdeki örnek önce dbstore2 adlı yeni bir container oluşturur, ardından arşivi bu container'ın volume'unda açar:

bash
docker run -v /dbdata --name dbstore2 ubuntu /bin/bash
docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"

Buradaki --strip 1 ayrıntısı önemlidir. Yedek alırken tar komutuna /dbdata yolu verildiği için arşivdeki her dosya dbdata/ ön ekiyle saklanır. Geri yüklerken önce /dbdata dizinine geçilip bu ön ekin ilk bileşeni atılır; böylece dosyalar /dbdata/dbdata/... gibi iç içe bir yapıya değil, doğrudan volume'un köküne açılır. Yedek komutunuzu değiştirirseniz, örneğin arşivi -C /dbdata . ile dizinin içinden alırsanız, geri yüklerken --strip gerekmez. Yedekle geri yükleme komutlarının birbiriyle uyumlu olduğunu, gerçek bir felaketten önce bir test volume'una geri yükleyerek denemeniz en sağlıklısıdır.

Belgeler bu tekniklerin yedekleme, taşıma ve geri yükleme testlerini dilediğiniz araçlarla otomatikleştirmek için kullanılabileceğini belirtiyor. Bir sunucuda bu, genellikle yukarıdaki komutu çağıran bir betik ve onu düzenli çalıştıran bir zamanlayıcı anlamına gelir.

Compose projesinde yedek servisi

Compose kullanıyorsanız yedek container'ını ayrı bir servis olarak tanımlayabilirsiniz. Compose belgelerindeki örnek de bir veritabanının veri dizinini, periyodik olarak yedeklenebilmesi için başka bir servisle paylaşmayı gösterir. Aşağıdaki dosyada yedek servisi db-data volume'unu salt okunur bağlar ve arşivi proje klasöründeki yedekler dizinine yazar. Servis bir profile bağlandığı için normal docker compose up sırasında başlamaz:

yaml
services:
  db:
    image: postgres:17
    volumes:
      - db-data:/var/lib/postgresql/data

  yedek:
    image: alpine:3.20
    command: ["tar", "cvf", "/backup/db-data.tar", "-C", "/data", "."]
    volumes:
      - db-data:/data:ro
      - ./yedekler:/backup
    profiles: ["yedek"]

volumes:
  db-data:

Bu dosyayı doğrularken profilin etkisi açıkça görülüyor. Profil belirtmeden servis listesi yalnızca db içeriyor, --profile yedek eklendiğinde yedek servisi de etkin hale geliyor:

bash
docker compose -p uygulama -f compose.yaml config --services
db
docker compose -p uygulama --profile yedek -f compose.yaml config --services
db
yedek

Yedeği almak için servisi profil ile tek seferlik çalıştırırsınız. Bu örnekte tar komutu -C /data . ile volume'un içinden arşivlediği için geri yüklerken --strip kullanmanız gerekmez. Profillerin genel kullanımı Docker Compose rehberi yazısında anlatılıyor.

bash
docker compose --profile yedek run --rm yedek

Volume'u başka bir sunucuya taşımak

Volume'u başka bir sunucuya taşımak: Eski sunucuda volume'u yukarıdaki gibi bir tar arşivine alın., Arşiv dosyasını kendi tercih ettiğiniz bir yöntemle (örneğin SSH üzerinden...

Docker belgelerinde taşıma için ayrı bir komut yoktur; yedekleme ve geri yükleme adımları taşıma için de kullanılır. Bir VPS'ten diğerine geçişte izleyeceğiniz sıra şöyledir:

  • Eski sunucuda volume'u yukarıdaki gibi bir tar arşivine alın.
  • Arşiv dosyasını kendi tercih ettiğiniz bir yöntemle (örneğin SSH üzerinden dosya kopyalama) yeni sunucuya aktarın.
  • Yeni sunucuda aynı adla bir volume oluşturun; Compose kullanıyorsanız proje adının aynı olmasına ya da name alanının sabit olmasına dikkat edin.
  • Geçici bir container ile volume'u ve arşivin bulunduğu dizini bağlayıp arşivi açın.
  • Uygulamayı yeni sunucuda başlatıp veriyi kontrol edin; eski sunucudaki volume'u ancak bundan sonra silin.

Volume'lar ile imajları karıştırmamak gerekir. docker save imajları dışa aktarmak içindir ve volume verisini içermez. Storage driver ya da image store değiştirirken belgelerin docker save veya registry'e push önermesi imajlar içindir; volume verisi için yine yukarıdaki tar yöntemini kullanırsınız. Docker'ın veri dizinini başka bir diske taşımak ise ayrı bir konudur ve daemon yapılandırmasında data-root ile yapılır; bunun için Docker daemon.json yapılandırma yazısına bakın.

Makineler arasında veri paylaşımı ve volume sürücüleri

Hata toleranslı uygulamalarda aynı servisin birden fazla kopyasının aynı dosyalara erişmesi gerekebilir. Belgeler bunun için iki yol sayıyor: uygulamaya dosyaları Amazon S3 gibi bir bulut nesne depolama sistemine yazacak mantık eklemek ya da NFS veya S3 gibi harici bir depolama sistemine yazmayı destekleyen bir sürücüyle volume oluşturmak. Volume sürücüleri, altta yatan depolama sistemini uygulama mantığından soyutlar. Örneğin NFS sürücüsü kullanan servislerinizi, uygulama kodunu değiştirmeden başka bir sürücüye geçirebilirsiniz.

Bu ayrım Swarm'da özellikle önemlidir. Bir servis başlatıp volume tanımladığınızda her servis container'ı kendi yerel volume'unu kullanır ve local sürücüyle bu container'lar veriyi paylaşamaz. Paylaşılan depolama yalnızca bunu destekleyen sürücülerle mümkündür. Ayrıca servisi silmek, servisin oluşturduğu volume'ları silmez. Swarm servisleri hakkında daha fazlası için Docker Swarm kurulumu yazısına bakabilirsiniz.

NFS volume: local sürücü ile

NFS için ek bir eklenti gerekmez; belgelerdeki örnekler local sürücüyü kullanır ve type=nfs ile NFS bağlaması yapar. Aşağıdaki iki örnek, 10.0.0.10 adresindeki NFS sunucusunun dışa aktardığı /var/docker-nfs dizinini bir servis oluştururken volume olarak bağlar. İlki NFSv3, ikincisi NFSv4 içindir:

bash
docker service create -d \
  --name nfs-service \
  --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,volume-opt=o=addr=10.0.0.10' \
  nginx:latest

docker service create -d \
    --name nfs-service \
    --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,"volume-opt=o=addr=10.0.0.10,rw,nfsvers=4,async"' \
    nginx:latest

İkinci örnekteki tırnaklama tesadüf değildir. --mount değeri virgülle ayrılmış bir liste olarak çözümlenir; sürücünün kendisi de o seçeneğinde virgüllü bir liste bekliyorsa bu iç liste dıştaki çözümleyiciden kaçırılmalıdır. Belgelerin kuralı şudur: volume-opt değerini çift tırnak içine, tüm --mount parametresini de tek tırnak içine alın. Bu kurala uymazsanız rw veya nfsvers=4 gibi parçalar ayrı --mount anahtarları sanılır ve komut hata verir ya da beklediğiniz seçenekler uygulanmaz.

Compose'da aynı işi üst düzey volumes bölümündeki driver_opts ile yaparsınız. Seçenekler sürücüye bağlıdır ve Compose bunları olduğu gibi sürücüye iletir. Aşağıdaki dosya Compose belgelerindeki NFS örneğinin bir servisle birlikte kullanımıdır ve doğrulamadan geçti:

yaml
services:
  app:
    image: nginx:latest
    volumes:
      - paylasim:/usr/share/nginx/html

volumes:
  paylasim:
    driver_opts:
      type: "nfs"
      o: "addr=10.40.0.199,nolock,soft,rw"
      device: ":/docker/example"

CIFS/Samba volume

Bir Samba paylaşımını, host üzerinde ayrıca bağlama noktası yapılandırmadan doğrudan Docker volume'u olarak bağlayabilirsiniz. Belgelerdeki örnek de local sürücüyü kullanır:

bash
docker volume create \
	--driver local \
	--opt type=cifs \
	--opt device=//uxxxxx.your-server.de/backup \
	--opt o=addr=uxxxxx.your-server.de,username=uxxxxxxx,password=*****,file_mode=0777,dir_mode=0777 \
	--name cifs-volume

device alanında IP yerine bir host adı kullanıyorsanız addr seçeneği zorunludur; Docker host adı çözümlemesini bu seçenek sayesinde yapar. Belgeler örnekteki kullanıcı adı ve parolayı yıldızlarla gizlemiş; gerçek komutta bu değerler komut satırında açıkça yazıldığı için kabuk geçmişinde kalabilir, sunucuya kimlerin erişebildiğini buna göre değerlendirin. Uzak bir yedek depolama alanını bu şekilde volume olarak bağlamak, yedek arşivlerinizi doğrudan sunucu dışına yazmanın da bir yoludur.

Adlandırılmış bind mount

Bazen bir volume'un Compose içinde sabit bir adı olsun ama verisi host üzerinde sizin belirlediğiniz bir dizinde dursun istersiniz. Compose belgeleri bunun için local sürücünün driver_opts alanıyla kurulan bir "adlandırılmış bind mount" deseni veriyor. type, o ve device anahtarları olduğu gibi yerel sürücüye iletilir. device mutlak bir host yolu olmalı ve önceden var olmalıdır:

yaml
services:
  app:
    image: nginx:latest
    volumes:
      - app-data:/usr/share/nginx/html

volumes:
  app-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /srv/app-data

Bu desen, yedekleme açısından iki dünyanın arasında bir yerde durur: Docker tarafında volume gibi davranır ve adıyla yönetilir, ama veri bilinen bir host dizinindedir. Tek bir servis için bir host yolunu bağlamanız yeterliyse doğrudan bind mount kullanmak daha sadedir; ayrıntılar için Docker bind mount yazısına bakın.

Eklenti tabanlı volume sürücüleri: rclone örneği

local sürücünün kapsamadığı depolama sistemleri için volume sürücü eklentileri kullanılır. Belgelerdeki örnek, SSH ile ulaşılabilen ikinci bir makineyi SFTP üzerinden volume olarak bağlayan rclone/docker-volume-rclone eklentisidir. Önce eklenti Docker host'una kurulur, sonra bu sürücüyle volume oluşturulur. Her sürücünün sıfır ya da daha fazla seçeneği olabilir ve her biri ayrı bir -o ile verilir:

bash
docker plugin install --grant-all-permissions rclone/docker-volume-rclone --alias rclone

docker volume create \
  -d rclone \
  --name rclonevolume \
  -o type=sftp \
  -o path=remote \
  -o sftp-host=1.2.3.4 \
  -o sftp-user=user \
  -o "sftp-password=$(cat file_containing_password_for_remote_host)"

Sürücü seçenek gerektiriyorsa volume'u container'a bağlarken -v değil --mount kullanmanız gerektiğini belgeler ayrıca not ediyor. --grant-all-permissions eklentinin istediği tüm izinleri onaylar; bir sunucuya eklenti kurmadan önce eklentinin kaynağını ve istediği yetkileri incelemeniz gerekir.

Blok aygıtını volume olarak bağlamak

Harici bir disk ya da disk bölümü gibi bir blok aygıtını da container'a volume olarak bağlayabilirsiniz. Belgeler bu prosedürü yalnızca bir örnek olarak sunuyor, genel bir uygulama olarak önermiyor ve ne yaptığınızdan emin değilseniz denememenizi söylüyor. Arka planda local sürücü ile --mount, Linux'un mount sistem çağrısını çağırır ve verdiğiniz seçenekleri değiştirmeden iletir; Docker çekirdeğin yerel bağlama özelliklerinin üstüne ek bir işlev eklemez. Bu yüzden volume-opt=type, volume-opt=device ve volume-opt=o değerlerini mount -t, aygıt ve -o parametreleri gibi düşünebilirsiniz:

bash
docker run \
  --mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4'

Container içinden mount komutunu doğrudan çalıştıramazsınız, çünkü container /dev/loop5 aygıtına erişemez; --mount seçeneğinin kullanılma sebebi budur. Belgelerdeki tam örnek bir dosyayı fallocate ile ayırıp mkfs.ext4 ile biçimlendirir, losetup ile döngü aygıtına çevirir ve en sonda losetup -d ile aygıtı kaldırır. losetup ile oluşturulan döngü aygıtının yeniden başlatmada kaybolduğunu unutmayın.

Sık yapılan hatalar

Sık yapılan hatalar: Yedeği hiç geri yüklememek, /var/lib/docker altını doğrudan kopyalamak, Volume yedeği için docker save kullanmak, Yeni sunucuda farklı proje adı kullanmak
  • Yedeği hiç geri yüklememek: tar yolları ve --strip ayarı uyumsuzsa geri yükleme beklenmedik bir dizin yapısı üretir. Yedek düzeninizi bir test volume'unda deneyin.
  • /var/lib/docker altını doğrudan kopyalamak: Belgeler volume verisine container dışından erişimi desteklenmeyen bir davranış olarak tanımlıyor.
  • Volume yedeği için docker save kullanmak: docker save imajları dışa aktarır, volume içeriğini içermez.
  • Yeni sunucuda farklı proje adı kullanmak: Compose volume adını proje adıyla önekler; geri yüklediğiniz volume'un adı uygulamanın aradığı adla aynı olmalı.
  • NFS seçeneklerini tırnaklamadan yazmak: Virgüllü o değeri çift tırnak, tüm --mount tek tırnak içinde olmalı.
  • Swarm'da local volume'un paylaşıldığını sanmak: Her görev kendi düğümündeki yerel volume'u kullanır; paylaşım için uygun bir sürücü gerekir.

Sık Sorulan Sorular

Çalışan bir container'ın volume'unu yedekleyebilir miyim?

Teknik olarak evet; volume aynı anda birden fazla container'a bağlanabildiği için yedek container'ı çalışan uygulamanın yanında da çalışabilir. Ancak uygulama yedek sırasında yazmaya devam ederse arşiv farklı anlarda okunmuş dosyalar içerir. Yazma yoğun veriler için uygulamayı durdurup yedeklemeyi ya da uygulamanın kendi dışa aktarma aracını kullanmayı değerlendirin.

--volumes-from ile volume adını doğrudan vermek arasında fark var mı?

--volumes-from, belirtilen container'ın tüm volume'larını aynı yollarla bağlar; anonim volume'ları yedeklerken pratiktir çünkü rastgele kimliği bilmeniz gerekmez. Adlandırılmış volume'da adı -v veya --mount ile doğrudan vermek daha açıktır ve salt okunur bağlamayı kolaylaştırır.

Yedek arşivini doğrudan uzak bir depolamaya yazabilir miyim?

Evet, yedek dizini olarak bir bind mount yerine CIFS ya da NFS ile oluşturulmuş bir volume bağlarsanız arşiv doğrudan o paylaşıma yazılır. Belgelerdeki CIFS örneği bu tür bir yedek alanını volume olarak bağlamayı gösteriyor.

Kullanılmayan volume'ları silmeden önce nasıl korurum?

Önemli volume'lara bir etiket koyup temizlikte docker volume prune --filter "label!=keep" gibi filtreler kullanabilirsiniz. Ayrıntılar için Docker prune ile disk temizleme yazısına bakın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Docker öğrenme rehberi · Docker volume kullanımı · Bind mount · Docker Swarm kurulumu · daemon.json yapılandırma · Docker Compose rehberi