Docker ile uygulama geliştirirken ya da production ortamı yönetirken, en sık yapılan hatalardan biri veri kalıcılığını göz ardı etmektir. Konteyner silindi, veri gitti. Konteyner yeniden oluşturuldu, veritabanı sıfırlandı. Bu senaryoyla bir kez karşılaşmak bile volume kullanımını hayatınızın vazgeçilmezi haline getirir. Compose ortamında kalıcı veri yönetimi, sadece bir konfigürasyon detayı değil, production-ready bir altyapının temel taşıdır.
Volume Nedir ve Neden Kritiktir?
Konteynerler doğası gereği geçicidir. Bir imajdan ayağa kalkar, işini yapar ve silindiğinde tüm dosya sistemi değişiklikleriyle birlikte ortadan kalkar. Bu, stateless uygulamalar için mükemmeldir. Ama bir PostgreSQL veritabanı, bir Redis instance’ı ya da kullanıcıların yüklediği dosyaları saklayan bir uygulama için bu durum felaket anlamına gelir.
Docker volume’ları bu sorunu çözmek için tasarlanmıştır. Volume, Docker tarafından yönetilen ve konteyner yaşam döngüsünden bağımsız olan bir depolama alanıdır. Konteyner silinse bile volume varlığını sürdürür. Yeni bir konteyner aynı volume’u bağladığında, tüm veriler yerli yerinde olur.
Compose ortamında volume yönetimi üç temel yöntemle yapılır:
- Named Volume: Docker tarafından yönetilen, isimlendirilmiş volume’lar
- Bind Mount: Host makinedeki bir dizini doğrudan konteynere bağlamak
- Anonymous Volume: İsimsiz, geçici volume’lar (genellikle önerilmez)
Named Volume ile Çalışmak
Named volume’lar, production ortamları için en güvenli ve taşınabilir seçenektir. Docker, bu volume’ları kendi yönetim alanında tutar ve host’un dosya sistemi yapısından bağımsız kılar.
Basit bir örnek üzerinden başlayalım:
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15
container_name: uygulama_db
environment:
POSTGRES_DB: uygulamam
POSTGRES_USER: dbadmin
POSTGRES_PASSWORD: guclu_sifre_buraya
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
volumes:
postgres_data:
driver: local
Burada postgres_data isimli bir volume tanımladık ve bunu PostgreSQL’in veri dizinine bağladık. volumes: bloğu iki yerde geçiyor: servis tanımının içinde (bağlama noktasını belirtmek için) ve dosyanın en altında (volume’un kendisini tanımlamak için).
Volume’u incelemek için:
# Volume listesi
docker volume ls
# Volume detayları
docker volume inspect postgres_data_compose_uygulama
# Hangi konteynerlerin kullandığını görmek
docker ps --filter volume=postgres_data
Gerçek Dünya Senaryosu: Web Uygulaması + Veritabanı + Cache
Şimdi daha gerçekçi bir senaryoya bakalım. Bir e-ticaret uygulaması düşünelim: Django backend, PostgreSQL veritabanı ve Redis cache. Her birinin farklı kalıcılık ihtiyaçları var.
# docker-compose.yml
version: '3.8'
services:
web:
build: .
container_name: eticaret_web
command: gunicorn eticaret.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_files:/app/staticfiles
- media_files:/app/media
environment:
- DATABASE_URL=postgresql://dbadmin:guclu_sifre@db:5432/eticaret
- REDIS_URL=redis://cache:6379/0
depends_on:
- db
- cache
restart: unless-stopped
db:
image: postgres:15
container_name: eticaret_db
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backup:/backup
environment:
POSTGRES_DB: eticaret
POSTGRES_USER: dbadmin
POSTGRES_PASSWORD: guclu_sifre
restart: unless-stopped
cache:
image: redis:7-alpine
container_name: eticaret_cache
command: redis-server --appendonly yes --requirepass redis_sifresi
volumes:
- redis_data:/data
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: eticaret_nginx
volumes:
- static_files:/var/www/static:ro
- media_files:/var/www/media:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
ports:
- "80:80"
- "443:443"
depends_on:
- web
restart: unless-stopped
volumes:
postgres_data:
redis_data:
static_files:
media_files:
Bu örnekte dikkat edilmesi gereken birkaç nokta var. static_files ve media_files volume’ları hem web hem de nginx servisi tarafından paylaşılıyor. Django’nun ürettiği statik dosyalar ve kullanıcıların yüklediği medya dosyaları, Nginx tarafından doğrudan sunulabiliyor. nginx servisindeki :ro eki, volume’un o konteyner tarafından sadece okunabilir olarak bağlandığını belirtir, bu iyi bir güvenlik pratiğidir.
Bind Mount Kullanımı ve Ne Zaman Tercih Edilmeli
Bind mount, host makinedeki gerçek bir dizini konteynere bağlar. Geliştirme ortamı için idealdir çünkü kod değişikliklerini anında konteynere yansıtır. Ama production’da dikkatli olmak gerekir.
# Geliştirme ortamı için docker-compose.dev.yml
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile.dev
container_name: uygulama_web_dev
volumes:
# Bind mount: kod değişiklikleri anında yansır
- .:/app
# Named volume: node_modules host'tan izole
- node_modules:/app/node_modules
ports:
- "3000:3000"
command: npm run dev
environment:
- NODE_ENV=development
db:
image: postgres:15
volumes:
# Production'da named volume kullanılır
- postgres_dev_data:/var/lib/postgresql/data
# Geliştirme için seed dosyaları
- ./sql/seed:/docker-entrypoint-initdb.d:ro
volumes:
node_modules:
postgres_dev_data:
node_modules için named volume kullanmak önemli bir trick’tir. Eğer tüm proje dizinini bind mount yaparsanız ve node_modules host’ta mevcutsa, konteyner içindeki node_modules‘u gölgeler. Bu özellikle farklı işletim sistemlerinde (macOS host, Linux container) sorun çıkarır.
Volume Driver’ları ve İleri Seviye Kullanım
Varsayılan local driver dışında, farklı senaryolar için özelleştirilmiş driver’lar kullanabilirsiniz.
# NFS volume kullanımı (shared storage senaryosu)
version: '3.8'
services:
app:
image: myapp:latest
volumes:
- shared_uploads:/app/uploads
volumes:
shared_uploads:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.100,rw,noatime,rsize=8192,wsize=8192
device: ":/mnt/nfs/uploads"
Birden fazla uygulama sunucusunun aynı dosyalara erişmesi gerektiğinde NFS volume’ları kurtarıcı olur. Özellikle yatay ölçekleme yapılan ortamlarda kullanıcı yüklemelerini ya da shared config dosyalarını bu şekilde yönetebilirsiniz.
External volume tanımı da önemli bir kavramdır. Docker Compose dışında oluşturulmuş bir volume’u compose projesine dahil edebilirsiniz:
# Önce volume'u elle oluştur
docker volume create --driver local
--opt type=none
--opt device=/srv/uygulama/veriler
--opt o=bind
onceden_olusturulmus_volume
# docker-compose.yml içinde external olarak referans ver
version: '3.8'
services:
app:
image: myapp:latest
volumes:
- onceden_olusturulmus_volume:/app/data
volumes:
onceden_olusturulmus_volume:
external: true
Bu yaklaşım, volume’un yaşam döngüsünü compose projesinden bağımsız tutmak istediğinizde faydalıdır. docker-compose down -v komutu bile bu volume’u silmez.
Volume Yedekleme ve Geri Yükleme
Production ortamında en önemli operasyonlardan biri yedeklemedir. Volume’ları yedeklemenin en pratik yöntemi, geçici bir konteyner ayağa kaldırıp tar ile sıkıştırmaktır.
# PostgreSQL volume yedeği alma
docker run --rm
-v eticaret_postgres_data:/kaynak:ro
-v $(pwd)/yedekler:/hedef
alpine
tar czf /hedef/postgres_yedek_$(date +%Y%m%d_%H%M%S).tar.gz -C /kaynak .
# Yedekten geri yükleme
docker run --rm
-v eticaret_postgres_data:/hedef
-v $(pwd)/yedekler:/kaynak:ro
alpine
tar xzf /kaynak/postgres_yedek_20240115_120000.tar.gz -C /hedef
PostgreSQL için daha doğru yöntem ise pg_dump kullanmaktır:
# pg_dump ile yedek alma (konteyner çalışıyorken)
docker exec eticaret_db pg_dump
-U dbadmin
-d eticaret
--format=custom
--compress=9
> yedekler/eticaret_$(date +%Y%m%d).dump
# pg_restore ile geri yükleme
docker exec -i eticaret_db pg_restore
-U dbadmin
-d eticaret
--clean
--if-exists
< yedekler/eticaret_20240115.dump
Yedekleme işlemini compose servisi olarak tanımlamak da mümkündür. Cron benzeri bir yedekleme servisi ekleyebilirsiniz:
version: '3.8'
services:
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: uygulamam
POSTGRES_USER: admin
POSTGRES_PASSWORD: sifre123
db_yedek:
image: postgres:15
volumes:
- ./yedekler:/yedekler
environment:
PGPASSWORD: sifre123
command: >
sh -c "while true; do
pg_dump -h db -U admin uygulamam > /yedekler/yedek_$$(date +%Y%m%d_%H%M%S).sql
echo 'Yedek alindi: '$$(date)
find /yedekler -name '*.sql' -mtime +7 -delete
sleep 86400
done"
depends_on:
- db
restart: unless-stopped
volumes:
postgres_data:
Volume İzleme ve Temizlik
Zamanla kullanılmayan volume’lar disk alanını doldurabilir. Özellikle sık sık docker-compose up --build çalıştıran geliştirme ortamlarında bu sorun baş gösterir.
# Kullanılmayan volume'ları listele
docker volume ls -f dangling=true
# Kullanılmayan tüm volume'ları sil (dikkatli kullanın!)
docker volume prune
# Compose projesine ait tüm volume'ları sil
docker-compose down --volumes
# Belirli bir volume'u sil
docker volume rm eticaret_postgres_data
# Volume disk kullanımını görüntüle
docker system df -v | grep -A 20 "VOLUME NAME"
# Tüm Docker kaynaklarının özet disk kullanımı
docker system df
Uyarı: docker-compose down --volumes komutu tüm named volume’larınızı siler. Production ortamında bu komutu çalıştırmadan önce mutlaka yedek alın.
Sık Yapılan Hatalar ve Çözümleri
Permission sorunları en yaygın sorunların başında gelir. Özellikle Linux’ta host kullanıcısı ile konteyner kullanıcısının UID/GID’leri uyuşmazsa dosyalara erişilemeyebilir.
# Bind mount ile permission sorunu çözümü
version: '3.8'
services:
app:
image: node:18-alpine
user: "1000:1000" # Host kullanıcısının UID:GID
volumes:
- ./uygulama:/app
working_dir: /app
command: node server.js
# Alternatif: Dockerfile'da kullanıcı oluşturma
# RUN addgroup -g 1000 appgroup &&
# adduser -u 1000 -G appgroup -D appuser
# USER appuser
Compose projeleri arası volume çakışması da sık karşılaşılan bir durumdur. Varsayılan olarak Docker Compose, volume isimlerinin önüne proje adını ekler. Bu davranışı kontrol etmek için:
# Proje adını açıkça belirt
docker-compose -p uretim up -d
# Ya da compose dosyasında
version: '3.8'
name: uretim_eticaret
services:
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
# Bu volume'un tam adı: uretim_eticaret_postgres_data olacak
Çoklu Ortam Yönetimi: Dev, Staging, Production
Aynı uygulamayı farklı ortamlarda çalıştırırken volume stratejinizi de ayarlamanız gerekir.
# docker-compose.yml (base)
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres_data:
# docker-compose.override.yml (development - otomatik merge edilir)
version: '3.8'
services:
db:
ports:
- "5432:5432" # Dev'de port expose et
volumes:
- ./sql/test_data:/docker-entrypoint-initdb.d:ro # Test verisi
web:
volumes:
- .:/app # Hot reload için bind mount
- /app/node_modules # node_modules'u koru
environment:
- DEBUG=true
# docker-compose.prod.yml
version: '3.8'
services:
db:
volumes:
- postgres_data:/var/lib/postgresql/data
- /etc/ssl/certs:/etc/ssl/certs:ro # SSL sertifikaları
volumes:
postgres_data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/ssd/postgresql # SSD üzerinde özel konum
Production compose dosyasını kullanmak için:
# Production ortamında çalıştırma
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Staging için
docker-compose -f docker-compose.yml -f docker-compose.staging.yml up -d
Volume Güvenliği
Volume güvenliği sıklıkla göz ardı edilir ama production ortamında kritik öneme sahiptir.
- Hassas verileri secret olarak yönetin: Veritabanı şifrelerini environment variable yerine Docker secret ile kullanın
- Read-only mount: Sadece okuma gerektiren durumlarda
:roekini kullanın - Volume şifreleme: Hassas veriler için LUKS şifrelenmiş disk üzerinde volume oluşturun
- Network isolation: Volume paylaşan servisler aynı ağda olmalı, gereksiz port expose etmeyin
# Docker secret kullanımı (Swarm mode gerektirir ama best practice)
version: '3.8'
services:
db:
image: postgres:15
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- postgres_data:/var/lib/postgresql/data
secrets:
db_password:
file: ./secrets/db_password.txt
volumes:
postgres_data:
Sonuç
Docker Compose ortamında kalıcı veri yönetimi, doğru yapılandırıldığında güçlü ve güvenilir bir altyapı sunar. Named volume’lar production için tercih edilmesi gereken yöntemdir; taşınabilirlik, izolasyon ve Docker’ın yerleşik yönetim araçlarından yararlanma açısından bind mount’lara göre belirgin avantajlar sağlar. Bind mount’lar ise geliştirme ortamında hot reload gibi ihtiyaçlar için vazgeçilmezdir.
Pratikte en çok işe yarayan kural şudur: production’da her stateful servisin (veritabanı, cache, dosya depolama) kendi named volume’u olmalı, bu volume’ların düzenli yedeği alınmalı ve yedeklerin geri yüklenebildiği periyodik olarak test edilmelidir. Çünkü alınmayan ya da geri yüklenemeyen yedek, hiç alınmamış yedekten farksızdır.
Compose projelerinizi name: direktifi ile açıkça isimlendirmek, birden fazla ortam yönetirken volume çakışmalarını önler. Çoklu compose dosyası yaklaşımıyla base konfigürasyonu ortamlara göre özelleştirmek ise hem kod tekrarını azaltır hem de yönetimi kolaylaştırır. Bu pratikleri bir kez oturttuğunuzda, konteyner tabanlı altyapınızda veri kaybı korkusuyla değil, güvenle çalışırsınız.