Docker ile çalışırken karşılaşılan en büyük problemlerden biri şudur: konteyner ölür, veriler de onunla birlikte gider. Geliştirme ortamında bu belki kabul edilebilir, ama production’da veritabanı verilerini, kullanıcı yüklemelerini ya da uygulama loglarını kaybetmek ciddi bir felakettir. İşte bu yüzden Docker volume yönetimi, her sysadmin’in sağlam kavraması gereken temel konulardan biridir.
Docker’da Veri Kalıcılığı Sorunu
Konteynerler doğası gereği geçicidir. Bir konteyner silip yeniden oluşturduğunuzda, içindeki tüm veriler gider. Bu tasarım aslında konteynerleri tutarlı ve tekrarlanabilir yapar, fakat gerçek dünya uygulamalarında her zaman kalıcı veriye ihtiyaç duyarız.
Bir MySQL konteynerini düşünün. Veritabanını docker run mysql ile başlatıp birkaç gün veri işledikten sonra docker rm yaparsanız, tüm veritabanı dosyaları yok olur. Ya da bir web uygulamasının /var/www/uploads dizinine kullanıcılar dosya yüklüyorsa ve siz konteyner güncellemesi yapıyorsanız, bütün yüklenen dosyalar kaybolur.
Docker bu sorunu çözmek için üç farklı mekanizma sunar:
- Volumes: Docker tarafından yönetilen, host’un
/var/lib/docker/volumes/altında saklanan alanlar - Bind mounts: Host dosya sistemindeki belirli bir dizini konteynere bağlama
- tmpfs mounts: Sadece RAM’de tutulan geçici depolama (Linux’a özgü)
Bu yazıda özellikle volumes ve bind mounts konularına odaklanacağız, çünkü production ortamlarında gerçekten işe yarayan mekanizmalar bunlar.
Docker Volumes: Temel Kullanım
Docker volumes, en çok önerilen kalıcı veri yönetimi yöntemidir. Docker bu volume’leri tamamen kendisi yönetir, siz sadece isim verip kullanırsınız.
Volume Oluşturma ve Listeleme
# Yeni bir volume oluştur
docker volume create uygulama-verisi
# Mevcut volume'leri listele
docker volume ls
# Volume hakkında detaylı bilgi al
docker volume inspect uygulama-verisi
docker volume inspect komutu size volume’ün nerede saklandığını, ne zaman oluşturulduğunu ve hangi driver’ı kullandığını gösterir. Çıktıda Mountpoint alanına bakarsanız genellikle /var/lib/docker/volumes/uygulama-verisi/_data gibi bir yol görürsünüz.
Konteyner ile Volume Kullanımı
Volume’ü konteynere bağlamanın iki yolu var: -v bayrağı ve --mount bayrağı. Ben --mount kullanmayı tercih ediyorum çünkü daha okunabilir ve hata yapmak daha zor.
# -v ile volume bağlama (eski yöntem)
docker run -d
--name mysql-db
-v mysql-verisi:/var/lib/mysql
-e MYSQL_ROOT_PASSWORD=gizlisifre
-e MYSQL_DATABASE=uygulamam
mysql:8.0
# --mount ile aynı işlem (önerilen yöntem)
docker run -d
--name mysql-db
--mount type=volume,source=mysql-verisi,target=/var/lib/mysql
-e MYSQL_ROOT_PASSWORD=gizlisifre
-e MYSQL_DATABASE=uygulamam
mysql:8.0
Şimdi bu MySQL konteynerini silin ve yeniden oluşturun:
# Konteyner sil (volume silinmez!)
docker rm -f mysql-db
# Aynı volume ile yeniden başlat
docker run -d
--name mysql-db
--mount type=volume,source=mysql-verisi,target=/var/lib/mysql
-e MYSQL_ROOT_PASSWORD=gizlisifre
-e MYSQL_DATABASE=uygulamam
mysql:8.0
# Verileriniz hala orada!
docker exec -it mysql-db mysql -u root -pgizlisifre -e "SHOW DATABASES;"
Konteyner yeniden oluşturuldu ama mysql-verisi volume’ü hala yerinde durduğu için tüm veritabanlarınız korunmuş oldu.
Bind Mounts: Host Dizinini Bağlama
Bind mount, host makinenizdeki belirli bir dizini doğrudan konteynere bağlamanızı sağlar. Geliştirme ortamlarında çok kullanışlıdır; kod değişikliklerini anında konteynere yansıtmak için harika bir yöntemdir.
# Geliştirme ortamı için bind mount
docker run -d
--name web-gelistirme
--mount type=bind,source=/home/ahmet/projeler/website,target=/var/www/html
-p 8080:80
nginx:latest
Artık /home/ahmet/projeler/website klasöründe yaptığınız her değişiklik anında konteynerdeki web sunucusuna yansır. PHP, statik HTML veya herhangi bir dosya tabanlı uygulama için bu yöntem geliştirme sürecini inanılmaz hızlandırır.
Bind Mount ile Production Senaryo: Log Yönetimi
Bir gerçek dünya senaryosu ele alalım. Bir Node.js uygulaması çalıştırıyorsunuz ve logları host makinede merkezi olarak toplamak istiyorsunuz:
# Log dizinini hazırla
mkdir -p /var/log/nodejs-uygulamam
chmod 777 /var/log/nodejs-uygulamam
# Uygulamayı log bind mount ile başlat
docker run -d
--name nodejs-api
--mount type=bind,source=/var/log/nodejs-uygulamam,target=/app/logs
-p 3000:3000
--restart unless-stopped
nodejs-uygulamam:latest
Artık konteyner içindeki /app/logs dizinine yazılan loglar, host makinenizin /var/log/nodejs-uygulamam klasöründe toplanır. Logrotate gibi araçlarla bu logları host üzerinde yönetebilir, ELK stack’e gönderebilir ya da basitçe tail -f ile takip edebilirsiniz.
Docker Compose ile Volume Yönetimi
Gerçek production ortamlarında single docker run komutları yerine Docker Compose kullanırsınız. Volume yönetimi Compose ile çok daha sistematik hale gelir.
version: '3.8'
services:
veritabani:
image: postgres:15
container_name: postgres-prod
environment:
POSTGRES_DB: uygulamam
POSTGRES_USER: dbkullanici
POSTGRES_PASSWORD: guclusifre123
volumes:
- postgres-verisi:/var/lib/postgresql/data
- ./db-init-scripts:/docker-entrypoint-initdb.d:ro
restart: unless-stopped
networks:
- backend-network
redis-cache:
image: redis:7-alpine
container_name: redis-cache
volumes:
- redis-verisi:/data
command: redis-server --appendonly yes --requirepass "redissifrem"
restart: unless-stopped
networks:
- backend-network
uygulama:
image: uygulamam:latest
container_name: web-uygulamam
volumes:
- kullanici-yuklemeleri:/app/uploads
- ./config/app.env:/app/.env:ro
- uygulama-loglar:/app/logs
ports:
- "3000:3000"
depends_on:
- veritabani
- redis-cache
restart: unless-stopped
networks:
- backend-network
- frontend-network
volumes:
postgres-verisi:
driver: local
redis-verisi:
driver: local
kullanici-yuklemeleri:
driver: local
uygulama-loglar:
driver: local
networks:
backend-network:
driver: bridge
frontend-network:
driver: bridge
Bu Compose dosyasında dikkat edilmesi gereken birkaç nokta var:
postgres-verisivolume’ü PostgreSQL’in tüm veritabanı dosyalarını tutar./db-init-scriptsbind mount:roflag’i ile read-only olarak bağlanmış; init scriptleri konteyner değiştiremezkullanici-yuklemelerivolume’ü kullanıcı yüklemelerini korur./config/app.envdosyası read-only bind mount ile konfigürasyon olarak enjekte edilmiş
Volume Yedekleme ve Geri Yükleme
Bu konuyu çoğu Docker eğitiminde atlıyorlar ama production için kritik öneme sahiptir. Volume’lerinizi nasıl yedekliyorsunuz?
Volume Yedekleme
# Volume'ü tar.gz olarak yedekle
docker run --rm
--mount type=volume,source=postgres-verisi,target=/kaynak
--mount type=bind,source=/backup,target=/hedef
alpine tar czf /hedef/postgres-yedek-$(date +%Y%m%d-%H%M%S).tar.gz -C /kaynak .
# Yedeklemenin başarılı olduğunu kontrol et
ls -lh /backup/
Bu komut geçici bir Alpine Linux konteyneri başlatır, postgres-verisi volume’ünü okur ve host’taki /backup klasörüne sıkıştırılmış arşiv olarak yazar. Konteyner işi bitince otomatik silinir (--rm sayesinde).
Volume Geri Yükleme
# Yedekten volume'ü geri yükle
docker run --rm
--mount type=volume,source=postgres-verisi-yeni,target=/hedef
--mount type=bind,source=/backup,target=/kaynak
alpine tar xzf /kaynak/postgres-yedek-20240115-143022.tar.gz -C /hedef
# Geri yükleme sonrası konteyner başlat
docker run -d
--name postgres-restored
--mount type=volume,source=postgres-verisi-yeni,target=/var/lib/postgresql/data
-e POSTGRES_PASSWORD=guclusifre123
postgres:15
Bu yedekleme stratejisini bir cron job ile otomatize edebilirsiniz:
# /etc/cron.d/docker-volume-backup dosyası
0 2 * * * root docker run --rm
--mount type=volume,source=postgres-verisi,target=/kaynak
--mount type=bind,source=/backup/postgres,target=/hedef
alpine tar czf /hedef/yedek-$(date +%Y%m%d).tar.gz -C /kaynak .
&& find /backup/postgres -name "yedek-*.tar.gz" -mtime +7 -delete
Bu cron job her gece saat 02:00’de yedekleme yapar ve 7 günden eski yedekleri temizler.
Volume Temizliği ve Disk Yönetimi
Zamanla kullanılmayan volume’ler birikerek disk alanını tüketir. Bu konuda proaktif olmak gerekir.
# Hiçbir konteynere bağlı olmayan volume'leri listele
docker volume ls -f dangling=true
# Kullanılmayan tüm volume'leri sil (DİKKAT: geri alınamaz!)
docker volume prune
# Tüm kullanılmayan Docker kaynaklarını temizle
docker system prune --volumes
# Disk kullanımını kontrol et
docker system df
docker system df -v # detaylı görünüm
docker system df komutu size şunu gösterir:
- Toplam imaj boyutu ve ne kadarının gereksiz olduğu
- Konteynerlerin kullandığı disk alanı
- Volume’lerin kullandığı alan
- Build cache boyutu
Production ortamında bu temizlik işlemini düzenli yapmak için bir script hazırlayabilirsiniz:
#!/bin/bash
# /usr/local/bin/docker-temizle.sh
TARIH=$(date +"%Y-%m-%d %H:%M")
LOG_DOSYA="/var/log/docker-temizlik.log"
echo "[$TARIH] Docker temizlik başlıyor..." >> $LOG_DOSYA
# Durmuş konteynerleri kaldır
DURDURULMUS=$(docker container prune -f --filter "until=24h" 2>&1)
echo "[$TARIH] Konteyner temizlik: $DURDURULMUS" >> $LOG_DOSYA
# Kullanılmayan imajları kaldır
IMAJLAR=$(docker image prune -f --filter "until=72h" 2>&1)
echo "[$TARIH] İmaj temizlik: $IMAJLAR" >> $LOG_DOSYA
# DİKKAT: Volume prune sadece dangling volume'leri siler
# production'da bunu otomatik çalıştırmayın!
# docker volume prune -f
echo "[$TARIH] Temizlik tamamlandı." >> $LOG_DOSYA
Read-Only Volume ve Güvenlik
Konteynerlere veri sağlarken her zaman minimum yetki prensibini uygulayın. Konteyner bir konfigürasyon dosyasını sadece okuyacaksa, neden yazma yetkisi veriyorsunuz?
# Konfigürasyon dosyalarını read-only bağla
docker run -d
--name guvenli-uygulama
--mount type=bind,source=/etc/myapp/config.yaml,target=/app/config.yaml,readonly
--mount type=bind,source=/etc/ssl/certs/myapp.crt,target=/app/ssl/cert.crt,readonly
--mount type=volume,source=uygulama-verisi,target=/app/data
--read-only
--tmpfs /tmp
--tmpfs /app/cache
guvenli-uygulama:latest
Burada --read-only bayrağı ile tüm konteyner dosya sistemi read-only yapılmış. Sadece belirtilen volume (/app/data) ve tmpfs alanları (/tmp ve /app/cache) yazılabilir. Bu yaklaşım güvenlik açısından çok daha sağlamdır; kötü niyetli bir süreç konteyner dosya sistemini değiştiremez.
Named Volume ile Farklı Konteynerler Arasında Veri Paylaşımı
Bazen birden fazla konteynerin aynı veriye erişmesi gerekir. Örneğin bir web sunucusu ve bir thumbnail oluşturucu aynı upload klasörüne erişmeli.
version: '3.8'
services:
web-sunucu:
image: nginx:latest
volumes:
- paylasilan-medya:/var/www/media:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
medya-isleyici:
image: medya-isleyici:latest
volumes:
- paylasilan-medya:/app/media
environment:
UPLOAD_PATH: /app/media
dosya-yukleme-api:
image: upload-api:latest
volumes:
- paylasilan-medya:/uploads
ports:
- "8080:8080"
volumes:
paylasilan-medya:
driver: local
Bu yapıda:
dosya-yukleme-apimedyaları/uploadsyoluna yazarmedya-isleyiciaynı volume’e/app/mediaüzerinden erişir, görselleri işlerweb-sunucuise sadece okuma yetkisiyle (:ro) medyaları sunar
Volume Driver ve Uzak Depolama
Local driver haricinde NFS, AWS EFS veya diğer depolama sistemlerini Docker volume olarak kullanabilirsiniz. Bu özellikle çoklu host ortamlarında veya Swarm modunda kritiktir.
# NFS volume oluştur
docker volume create
--driver local
--opt type=nfs
--opt o=addr=192.168.1.100,rw,nfsvers=4
--opt device=:/exports/docker-volumes
nfs-paylasilan-veri
# Bu volume'ü kullanan konteyner başlat
docker run -d
--name nfs-uygulama
--mount source=nfs-paylasilan-veri,target=/app/data
uygulamam:latest
Compose dosyasında NFS volume tanımı:
volumes:
paylasilan-nfs:
driver: local
driver_opts:
type: nfs
o: "addr=192.168.1.100,rw,nfsvers=4"
device: ":/exports/docker-volumes"
Bu yöntemle birden fazla sunucudaki konteynerler aynı NFS paylaşımına erişebilir.
Yaygın Hatalar ve Çözümleri
Yıllar içinde gördüğüm en sık yapılan hatalar:
Volume izin sorunları: Konteyner içindeki süreç farklı bir UID ile çalışabilir ve volume’e yazamayabilir.
# Sorunlu durum - nginx 101 UID ile çalışır
docker run -d
--name nginx-sorun
-v /host/html:/usr/share/nginx/html
nginx:latest
# Çözüm 1: Host dizin izinlerini düzenle
sudo chown -R 101:101 /host/html
# Çözüm 2: Konteyner içinde user ayarla
docker run -d
--name nginx-duzgun
--user 101:101
--mount type=bind,source=/host/html,target=/usr/share/nginx/html
nginx:latest
Yanlış volume path: Volume’ü bağlarken konteyner içindeki yolu yanlış yazarsanız Docker yeni ve boş bir volume oluşturur, mevcut volume’ünüzü bağlamaz. Her zaman docker inspect ile kontrol edin.
# Konteyner volume bağlantılarını kontrol et
docker inspect --format='{{json .Mounts}}' konteyner-adi | python3 -m json.tool
Sonuç
Docker volume yönetimi başta karmaşık görünse de aslında birkaç temel prensibi kavrayınca oldukça sistematik bir hal alıyor. Özetleyecek olursak:
- Named volumes production için tercih edilen yöntemdir; taşınabilir ve Docker tarafından yönetilir
- Bind mounts geliştirme ortamları ve log toplama gibi senaryolar için idealdir
- Read-only bağlama her zaman güvenliği artırır; mümkün olduğunda kullanın
- Düzenli yedekleme olmadan volume kullanmak yarım iştir; yedekleme stratejinizi ilk günden kurun
- Disk temizliği proaktif yapılmalı; dangling volume’ler sessiz sedasız disk doldurur
- Volume inspect ve system df komutlarını sık kullanın; ne olduğunu her zaman bilin
Production’a geçmeden önce volume stratejinizi net olarak belirleyin: hangi veriler kalıcı olacak, hangileri geçici, yedekleme sıklığı ne olacak, kim yönetecek. Bu soruların cevapları olmadan Docker ile çalışmak, zaman bombası kurmak gibidir. Ama doğru kurgulanmış bir volume yapısıyla Docker’ın getirdiği esnekliği eksiksiz kullanabilirsiniz.