Docker’da Kalıcı Veri Yönetimi: Volume Kullanımı ve Bağlama

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-verisi volume’ü PostgreSQL’in tüm veritabanı dosyalarını tutar
  • ./db-init-scripts bind mount :ro flag’i ile read-only olarak bağlanmış; init scriptleri konteyner değiştiremez
  • kullanici-yuklemeleri volume’ü kullanıcı yüklemelerini korur
  • ./config/app.env dosyası 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-api medyaları /uploads yoluna yazar
  • medya-isleyici aynı volume’e /app/media üzerinden erişir, görselleri işler
  • web-sunucu ise 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.

Yorum yapın