← Kursa Dön
📄 Text · 35 min

Named Volumes — Veri Kalıcılığı

Bir önceki derste Docker'da veri kalıcılığının neden önemli olduğunu gördük ve üç farklı yöntemi tanıdık. Bu derste Named Volume'ları derinlemesine inceleyeceğiz çünkü production ortamında en çok kullanacağın yöntem bu.

Named Volume'u bir banka kasası gibi düşün. Evin yansa bile kasadaki mücevherler güvende. Kasanın anahtarı sende, banka kasayı yönetiyor. Sen sadece "42 numaralı kasayı aç" diyorsun — nerede olduğunu, nasıl korunduğunu banka halleder. Docker Named Volume da tam böyle: sen bir isim veriyorsun (pgdata), Docker nereye koyacağını biliyor ve yönetiyor.


Named Volume vs Anonymous Volume

Docker'da volume oluştururken isim vermezsen, Docker rastgele bir hash verir. Buna anonymous volume denir ve yönetimi zordur:

# Anonymous volume — adı yok, Docker rastgele hash verir
docker run -v /data nginx:alpine
docker volume ls
DRIVER    VOLUME NAME
local     a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6

Bu ismi hatırlamak, referans etmek, yedeklemek — hepsi zor. Kaybolursa arama. Şimdi named volume'a bakalım:

# Named volume — anlamlı isim, kolayca yönetilebilir
docker run -v my-data:/data nginx:alpine
docker volume ls
DRIVER    VOLUME NAME
local     my-data

İsim var, anlamlı, her zaman bulunabilir. Named volume'lar "isimle açılmış banka hesabı" gibi — her zaman erişebilirsin.


Named Volume Oluşturma ve Kullanma

Named volume oluşturmanın iki yolu var:

Yöntem 1 — Açıkça oluştur (explicit):

docker volume create pgdata

Yöntem 2 — Container çalıştırırken otomatik (implicit):

docker run -d -v pgdata:/var/lib/postgresql/data postgres:16

Eğer pgdata zaten varsa onu kullanır, yoksa otomatik oluşturur. İkinci yöntem daha yaygın çünkü genelde container'ı başlatırken volume'u da belirtirsin.

Explicit oluşturmanın avantajı: label ekleyebilirsin, driver seçebilirsin:

docker volume create \
    --label project=myapp \
    --label environment=production \
    --label managed-by=devops \
    myapp-pgdata

Bu label'lar sonradan filtreleme yaparken çok işe yarar:

docker volume ls --filter label=project=myapp
DRIVER    VOLUME NAME
local     myapp-pgdata

Volume'un Host'taki Yeri

Named volume oluşturduğunda Docker onu host'ta belirli bir dizine koyar. Bunu docker volume inspect ile görebilirsin:

docker volume inspect pgdata
[
    {
        "CreatedAt": "2025-02-15T10:30:00Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
        "Name": "pgdata",
        "Options": {},
        "Scope": "local"
    }
]

Mountpoint alanı, verinin fiziksel olarak nerede tutulduğunu gösterir. Ama bu dizine doğrudan erişmek kötü pratiktir. Docker'ın iç yapısı gelecekte değişebilir. Bunun yerine geçici bir container üzerinden erişim yap:

# ✅ Doğru yöntem — geçici container ile volume içeriğini gör
docker run --rm -v pgdata:/data alpine ls -la /data

# ❌ Yanlış yöntem — Docker'ın iç dizinine doğrudan erişim
sudo ls /var/lib/docker/volumes/pgdata/_data/

Veritabanlarıyla Named Volume Kullanımı

Named volume'ların en kritik kullanım alanı veritabanlarıdır. Container ölse bile veri yaşamalı. Her veritabanı için nasıl kullanılacağını görelim.

PostgreSQL

docker volume create pg-production

docker run -d \
    --name postgres-prod \
    -e POSTGRES_USER=appuser \
    -e POSTGRES_PASSWORD=securepass123 \
    -e POSTGRES_DB=production \
    -e PGDATA=/var/lib/postgresql/data/pgdata \
    -v pg-production:/var/lib/postgresql/data \
    -p 5432:5432 \
    postgres:16-alpine

Burada PGDATA environment variable'ına dikkat. PostgreSQL'in veri dizinini volume'un içindeki bir alt dizine yönlendiriyoruz. Bu, PostgreSQL'in "data directory is not empty" hatası vermesini önler.

Şimdi container'ı öldürüp verinin hayatta kaldığını kanıtlayalım:

# Veri oluştur
docker exec -it postgres-prod psql -U appuser -d production -c "
    CREATE TABLE users (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100) NOT NULL,
        email VARCHAR(255) UNIQUE NOT NULL
    );
    INSERT INTO users (name, email) VALUES
        ('Ahmet Yılmaz', 'ahmet@example.com'),
        ('Elif Kaya', 'elif@example.com');
"

# Container'ı öldür
docker rm -f postgres-prod

# Yeni container, AYNI volume
docker run -d \
    --name postgres-prod-v2 \
    -e POSTGRES_USER=appuser \
    -e POSTGRES_PASSWORD=securepass123 \
    -e PGDATA=/var/lib/postgresql/data/pgdata \
    -v pg-production:/var/lib/postgresql/data \
    -p 5432:5432 \
    postgres:16-alpine

# Veri kontrolü
docker exec -it postgres-prod-v2 psql -U appuser -d production -c "SELECT * FROM users;"
 id |     name      |       email
----+---------------+---------------------
  1 | Ahmet Yılmaz  | ahmet@example.com
  2 | Elif Kaya      | elif@example.com

Veriler sapasağlam! Container yeni ama veri eski — volume sayesinde.

MySQL

docker volume create mysql-data

docker run -d \
    --name mysql-prod \
    -e MYSQL_ROOT_PASSWORD=rootpass \
    -e MYSQL_DATABASE=myapp \
    -e MYSQL_USER=appuser \
    -e MYSQL_PASSWORD=apppass \
    -v mysql-data:/var/lib/mysql \
    -p 3306:3306 \
    mysql:8.0

Redis — Kalıcı Mod

Redis varsayılan olarak sadece RAM'de çalışır. Ama appendonly ile diske de yazmasını sağlayabilirsin:

docker volume create redis-data

docker run -d \
    --name redis-prod \
    -v redis-data:/data \
    -p 6379:6379 \
    redis:7-alpine \
    redis-server --appendonly yes --appendfsync everysec

--appendonly yes ile Redis her değişikliği AOF (Append Only File) dosyasına yazar. Volume sayesinde restart'larda veri kaybı olmaz.


Volume Pre-Population — Otomatik İlk Doldurma

Named volume'ların ilginç bir özelliği var: ilk bağlandığında volume boşsa, Docker container image'ındaki hedef dizindeki dosyaları otomatik olarak volume'a kopyalar. Buna "pre-population" denir.

Bunu bir örnekle görelim. Nginx image'ında /usr/share/nginx/html/ dizininde varsayılan index.html ve 50x.html dosyaları var:

# İlk kullanım — volume boş → dosyalar volume'a kopyalanır
docker run -d --name web1 \
    -v my-html:/usr/share/nginx/html \
    -p 8080:80 \
    nginx:alpine

# Volume'un içeriğine bakalım
docker run --rm -v my-html:/data alpine ls /data
50x.html
index.html

Nginx'in varsayılan dosyaları volume'a kopyalandı. Ama şimdi dikkatli ol — ikinci bir container aynı volume'u kullanırsa ne olur?

# İkinci container — volume ZATEN DOLU → pre-population ÇALIŞMAZ
docker run -d --name web2 \
    -v my-html:/usr/share/nginx/html \
    -p 8081:80 \
    httpd:alpine

httpd (Apache) image'ının kendi varsayılan dosyaları var ama bunlar volume'a kopyalanmaz çünkü volume zaten dolu. web2, Nginx'in dosyalarını servis edecek.

Bu davranış bazen kafa karıştırıcı olabilir. Özellikle şu senaryoda:

  1. myapp:v1 ile volume oluşturuldu, /app/static dosyaları volume'a kopyalandı

  2. myapp:v2'de /app/static güncellendi — yeni CSS, yeni JS

  3. Container'ı v2 ile yeniden çalıştırdın

  4. Ama volume hâlâ v1'in dosyalarını içeriyor!

Çözüm: Statik dosyalar için volume kullanma, image katmanında tut. Ya da container'ın entrypoint script'inde dosyaları manuel güncelle.


Container'lar Arası Volume Paylaşımı

Birden fazla container aynı named volume'u kullanabilir. Bu, bazı senaryolarda çok faydalı:

Log Toplama Senaryosu

docker volume create app-logs

# Uygulama — logları volume'a yazar
docker run -d --name myapp \
    -v app-logs:/var/log/app \
    alpine sh -c 'while true; do echo "$(date) - Log entry" >> /var/log/app/app.log; sleep 5; done'

# Log toplayıcı — logları okur ve işler
docker run -d --name log-shipper \
    -v app-logs:/logs:ro \
    alpine sh -c 'tail -f /logs/app.log'

Uygulama yazar, log toplayıcı okur. Toplayıcı :ro ile mount etmiş — sadece okuyabilir, yanlışlıkla silme riski yok.

volumes-from ile Miras Alma

Bir container'ın tüm volume'larını başka bir container'a devretmek için --volumes-from kullanabilirsin:

docker create --name data-store \
    -v uploads:/app/uploads \
    -v config:/app/config \
    alpine

docker run -d --name worker1 --volumes-from data-store myworker
docker run -d --name worker2 --volumes-from data-store myworker

worker1 ve worker2 aynı /app/uploads ve /app/config dizinlerini görür. Bu eski bir pattern ama hâlâ geçerli.

⚠️ Dikkat: Birden fazla container aynı volume'a yazarsa race condition riski var. Özellikle veritabanı dosyalarını asla iki container'a aynı anda yazdırma — veri bozulur!


Docker Compose'da Named Volumes

Docker Compose'da volume tanımı iki yerde yapılır: servis seviyesinde (kullanım) ve dosyanın en altında üst seviyede (tanım).

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data

volumes:
  pgdata:
  redis-data:
    labels:
      com.example.backup: "daily"

En alttaki volumes: bölümü zorunlu — burada volume'ları tanımlıyorsun. Tanımlamadan kullanırsan Compose hata verir.

External Volume

Bazen volume'u Compose'un dışında oluşturmak istersin — mesela bir migration script'i ile:

volumes:
  pgdata:
    external: true

external: true dediğinde, docker compose up volume oluşturmaz. Volume'un zaten var olmasını bekler. Yoksa hata verir. Bu, production'da güvenli bir yaklaşım — yanlışlıkla yeni boş volume oluşturulmasını önler.

Volume Yaşam Döngüsü

Bu çok önemli bir konu. Compose'da down komutuyla ne olur?

# Container'ları sil, volume'ları KORU
docker compose down

# Container + volume SİL — DİKKAT!
docker compose down -v

-v flag'i volume'ları da siler. Production'da asla düşünmeden `-v` kullanma! Veritabanı verilerini geri dönüşümsüz kaybedebilirsin.


Volume İzin (Permission) Sorunları

Bu, yeni başlayanların en çok karşılaştığı sorunlardan biri. Container non-root user ile çalışıyor ama volume'a yazamıyor:

docker run -d --name myapp \
    -v app-data:/data \
    --user 1000:1000 \
    myapp:latest
# Error: permission denied: /data

Neden? Volume ilk oluşturulduğunda root sahipliğinde oluşuyor. Ama container 1000:1000 user'ı ile çalışıyor ve yazma izni yok.

Çözüm 1 — Dockerfile'da dizini hazırla:

FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
RUN mkdir -p /data && chown app:app /data
VOLUME /data
USER app

Çözüm 2 — Init container ile izinleri ayarla:

docker run --rm -v app-data:/data alpine chown -R 1000:1000 /data

Bu geçici container volume'un sahipliğini değiştirir, sonra asıl container sorunsuz çalışır.


Volume Adı Çakışması

İki farklı projede aynı volume adını kullanırsan başın ağrır. Mesela:

cd ~/project-a && docker compose up -d
# "pgdata" oluşturuldu — project-a'nın verileri

cd ~/project-b && docker compose up -d
# "pgdata" ZATEN VAR — project-b, project-a'nın verilerini kullanıyor! 😱

Docker Compose bunu otomatik çözer — proje adını prefix olarak ekler:

project-a_pgdata
project-b_pgdata

Compose'un bu davranışını -p ile de kontrol edebilirsin:

docker compose -p myproject up -d
# → myproject_pgdata

Volume Doluluk Sorunu

Volume'lar host'un disk alanını kullanır. Zamanla birikip diski doldurabilir:

# Docker'ın disk kullanımını gör
docker system df -v

# Belirli volume'un boyutunu öğren
sudo du -sh /var/lib/docker/volumes/pgdata/_data/
2.3G    /var/lib/docker/volumes/pgdata/_data/

Kullanılmayan volume'ları düzenli temizle:

# Önce neyin silineceğini gör
docker volume ls --filter dangling=true

# Sonra temizle
docker volume prune

İleri Düzey: Volume Driver'ları

Default local driver host dosya sistemini kullanır. Ama farklı driver'larla NFS, cloud storage gibi uzak depolamalara da bağlanabilirsin:

# NFS mount
docker volume create \
    --driver local \
    --opt type=nfs \
    --opt o=addr=192.168.1.100,rw,nfsvers=4 \
    --opt device=:/exports/docker-data \
    nfs-storage

# tmpfs volume (RAM disk — hızlı, geçici)
docker volume create \
    --driver local \
    --opt type=tmpfs \
    --opt device=tmpfs \
    --opt o=size=500m \
    ramdisk

Cloud ortamlarında AWS EBS, Azure Disk gibi storage'ları Docker volume olarak bağlayan 3rd party driver'lar da var. Ama bu kurs kapsamında local driver yeterli.


Volume Kullanım Pattern'ları

Gerçek projelerde karşılaşacağın birkaç pattern'ı paylaşayım.

Init Container ile Volume Hazırlama

Uygulama başlamadan önce volume'a config dosyalarını kopyalamak isteyebilirsin:

# Önce config'leri volume'a kopyala
docker run --rm \
    -v app-config:/config \
    myapp:latest \
    cp -r /app/default-config/. /config/

# Sonra uygulamayı başlat
docker run -d --name myapp \
    -v app-config:/app/config:ro \
    myapp:latest

Read-Only Veri Dağıtımı

Birden fazla worker'ın aynı ML modelini kullanması:

docker volume create ml-model

# Modeli bir kez yükle
docker run --rm -v ml-model:/model \
    alpine wget -O /model/model.onnx https://storage.example.com/model.onnx

# Birden fazla worker aynı modeli read-only kullanır
for i in 1 2 3 4; do
    docker run -d --name worker-$i \
        -v ml-model:/model:ro \
        ml-inference:latest
done

4 worker aynı modeli paylaşıyor, hiçbiri değiştiremiyor (read-only). Verimli ve güvenli.


Best Practices — Altın Kurallar

Deneyimlerden süzülmüş birkaç önemli kural:

İsimlendirme: Volume isimlerini anlamlı tut. data veya volume1 değil, pgdata, redis-cache, app-uploads gibi ne olduğu belli isimler ver.

Label'la: Proje, ortam ve backup politikası bilgisini label olarak ekle. Çok sayıda volume olduğunda filtreleme çok işe yarar.

Read-only: Mümkün olan her yerde :ro kullan. Container'ın yazmasına gerek yoksa yazma izni verme.

Bir veritabanı, bir volume: Her veritabanı instance'ı kendi volume'unda olsun. İki farklı veritabanını aynı volume'a koymak sorun yaratır.

Prune dikkatli: docker volume prune'u production'da çalıştırmadan önce mutlaka --filter dangling=true ile kontrol et.

Compose'da `-v` dikkatli: docker compose down -v'yi düşünmeden çalıştırma — veritabanı verileri gider!


Bu Derste Ne Öğrendik?

  • Named Volume, Docker tarafından yönetilen, isimlendirilmiş kalıcı depolama alanıdır.

  • Container silinse bile volume ve içindeki veri korunur.

  • Pre-population sadece volume boşken çalışır — güncelleme yaparken dikkatli ol.

  • Birden fazla container aynı volume'u paylaşabilir ama concurrent write konusuna dikkat.

  • Compose'da down -v volume'ları da siler — production'da çok dikkatli kullan.

  • Volume izin sorunları en yaygın hatalardan biri — Dockerfile'da veya init container'da çöz.

  • Volume isimlerini anlamlı tut, label ekle, düzenli prune yap.

Bir sonraki derste Bind Mount'ları derinlemesine inceleyeceğiz — hot reload geliştirme ortamı, konfigürasyon yönetimi ve macOS/Windows performans çözümleri.