← Kursa Dön
📄 Text · 30 min

Docker Compose Nedir? Çoklu Container Orkestrasyonu

Şimdiye kadar her container'ı ayrı ayrı docker run komutuyla başlattık. Bir web servisi başlat, bir veritabanı başlat, bir Redis başlat, network oluştur, volume oluştur, hepsini birbirine bağla... Küçük bir proje için bile 5-10 komut yazıyorduk. Peki bir projede 10 servis varsa? Her birinin port'u, volume'u, environment variable'ı, network bağlantısı farklıysa?

Bu noktada docker run komutlarını elle yazmak kabus haline gelir. Ve bir gün "bu container'ı hangi parametrelerle başlatmıştım?" diye düşünmeye başlarsın.

İşte Docker Compose tam bu problemi çözer. Tüm container'larını, network'lerini, volume'larını tek bir YAML dosyasında tanımlarsın. Sonra docker compose up dersin — her şey ayağa kalkar. docker compose down — her şey durur. Bu kadar basit.


Docker Compose Neden Var?

Bir restoran açıyorsun diyelim. Sadece aşçı yetmez — garson lazım, kasiyer lazım, bulaşıkçı lazım. Hepsini ayrı ayrı aramak, "sen saat 8'de gel, sen 9'da, sen şu bölümde çalış" demek yerine bir kadro listesi yapsan? Herkesin görevi, çalışma saati, bağlı olduğu departman — tek bir belgede. Ve "hadi başlayalım" dediğinde herkes işinin başına geçse?

Docker Compose tam bu kadro listesi. Her servis (container) ne yapacak, hangi image'ı kullanacak, hangi portları açacak, hangi volume'a bağlanacak, hangi servise bağımlı — hepsi docker-compose.yml dosyasında tanımlı.

Önce Docker Compose'un kurulu olduğunu doğrulayalım:

docker compose version
Docker Compose version v2.24.0

Modern Docker'da Compose dahili olarak gelir. Eğer eski docker-compose (tire ile) komutunu kullanıyorsan, yeni docker compose (boşluk ile) versiyonuna geç — eski versiyon artık güncellenmiyorum.


İlk docker-compose.yml Dosyası

En basit halinden başlayalım. Bir Nginx web sunucusu çalıştıralım:

# docker-compose.yml
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro

Bu kadar. Üç satır servis tanımı. Şimdi başlatalım:

docker compose up -d

-d arka planda (detached) çalıştırır. Tarayıcıda http://localhost:8080 adresine git — Nginx çalışıyor.

Durdurmak için:

docker compose down

Tüm container'lar durur ve silinir. Network'ler de temizlenir. Ama volume'lar kalır — verilerin güvende.

Şimdi aynı şeyi docker run ile yapmak isteseydin:

docker run -d --name web -p 8080:80 -v $(pwd)/html:/usr/share/nginx/html:ro nginx:alpine

Tek container için fark az. Ama birden fazla servis olduğunda Compose'un değeri katlanarak artar.


Gerçekçi Bir Proje: API + Veritabanı + Cache

Şimdi gerçek bir senaryo kuralım. Bir backend API, PostgreSQL veritabanı ve Redis cache — üçü birlikte çalışacak:

# docker-compose.yml
services:
  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://admin:secret@db:5432/myapp
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - frontend
      - backend

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

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

volumes:
  pgdata:
  redis-data:

networks:
  frontend:
  backend:
    internal: true

Bu dosyada çok şey oluyor, adım adım inceleyelim.

`services` bölümü: Üç servis tanımladık — api, db, redis. Her biri bir container olacak.

`api` servisi: build: ./api diyoruz, yani ./api dizinindeki Dockerfile'dan image build edilecek. Port mapping, environment variable'lar ve bağımlılıklar tanımlı.

`db` servisi: Hazır PostgreSQL image'ı kullanıyor. Volume ile veri kalıcılığı sağlıyoruz. Ve çok önemli bir özellik: healthcheck. Bu, veritabanının gerçekten hazır olup olmadığını kontrol eder.

`depends_on`: API'nin veritabanına bağımlılığını tanımlıyor. condition: service_healthy diyoruz — yani API, veritabanı sağlıklı olana kadar başlamayacak. Bu çok önemli çünkü PostgreSQL'in başlaması birkaç saniye sürer ve API hemen bağlanmaya çalışırsa "connection refused" hatası alır.

`networks`: İki network tanımladık. frontend dış dünyaya açık, backend internal (internet erişimi yok). API her iki network'te — köprü rolü oynuyor. Veritabanı ve Redis sadece backend'de — dışarıdan erişilemez.

`volumes`: İki named volume — veritabanı ve Redis verileri için. Container'lar silinse bile veriler kalır.

Başlatalım:

docker compose up -d --build

--build flag'i, Dockerfile'dan build edilen servisleri yeniden build eder. İlk çalıştırmada veya kod değişikliği sonrasında kullan.


Compose Komutları — Günlük Kullanım

Docker Compose'un en sık kullanacağın komutlarını görelim.

Başlatma ve durdurma:

# Tüm servisleri başlat (arka planda)
docker compose up -d

# Belirli servisleri başlat
docker compose up -d api db

# Yeniden build et ve başlat
docker compose up -d --build

# Tüm servisleri durdur ve temizle
docker compose down

# Volume'ları da sil (DİKKAT!)
docker compose down -v

Durum ve loglar:

# Servis durumları
docker compose ps

# Tüm loglar
docker compose logs

# Belirli servisin logları
docker compose logs api

# Canlı log takibi
docker compose logs -f api db

# Son 50 satır
docker compose logs --tail 50 api

Servis içinde komut çalıştırma:

# Servis içinde shell aç
docker compose exec api sh

# Veritabanına bağlan
docker compose exec db psql -U admin -d myapp

# Tek seferlik komut (geçici container oluşturur)
docker compose run --rm api npm test
docker compose run --rm api node scripts/seed.js

exec çalışan container'da komut çalıştırır. run ise yeni bir geçici container oluşturur — test ve migration gibi tek seferlik işler için ideal.

Yönetim:

# Servis yeniden başlat
docker compose restart api

# Sadece durdur (silmeden)
docker compose stop

# Durdurulmuş servisleri başlat
docker compose start

# Build (başlatmadan)
docker compose build
docker compose build --no-cache  # Cache'siz

Environment Variables — Konfigürasyon Yönetimi

Şifreleri, API key'leri ve ortam ayarlarını Compose dosyasına hardcode etmek kötü pratik. Bunun yerine .env dosyası kullan:

# .env (docker-compose.yml'nin yanında)
DB_USER=admin
DB_PASS=Kj8mN2pQr5v
DB_NAME=myapp
REDIS_PASS=Rm3hT7nYw2b
JWT_SECRET=a1b2c3d4e5f6g7h8
# docker-compose.yml
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASS}
      POSTGRES_DB: ${DB_NAME}

  api:
    build: ./api
    environment:
      DATABASE_URL: postgres://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}
      JWT_SECRET: ${JWT_SECRET}

Docker Compose, .env dosyasını otomatik okur ve ${VAR} yerlerine değerleri koyar. Final konfigürasyonu görmek için:

docker compose config

Bu komut tüm substitution'ları çözümler ve son hali gösterir. Deploy öncesi hataları yakala.

⚠️ Önemli: .env dosyasını asla git'e commit etme! .gitignore'a ekle. Bunun yerine .env.example dosyası oluştur ve onu commit et — yeni geliştirici template olarak kullanır:

# .gitignore
.env
!.env.example

Servislere .env dosyasından environment variable yüklemek için env_file de kullanabilirsin:

services:
  api:
    env_file:
      - .env
      - .env.production  # Override

Build Konfigürasyonu

Servislerini hazır image'dan değil de kendi Dockerfile'ından build etmek istediğinde:

services:
  api:
    # Basit — dizini göster
    build: ./api

    # Detaylı — tüm seçenekler
    build:
      context: ./api
      dockerfile: Dockerfile.prod
      args:
        NODE_ENV: production
        VERSION: "1.2.3"
      target: production  # Multi-stage build'de hangi stage

    # Build sonrası image'ı tag'le
    image: myregistry/api:1.2.3

target: production çok kullanışlı. Multi-stage Dockerfile'da development ve production stage'lerin varsa, hangi stage'i build edeceğini belirtirsin.


Healthcheck ve depends_on

Bu ikili birlikte çalışarak servis bağımlılıklarını doğru şekilde yönetir. Bunu anlamak çok önemli çünkü yaygın bir sorunun çözümü burada.

Sorun: API başladı ama veritabanı henüz bağlantı kabul etmiyor. API "connection refused" hatası alıyor ve çöküyor.

Çözüm: Healthcheck + depends_on condition.

services:
  api:
    depends_on:
      db:
        condition: service_healthy  # DB sağlıklı olana kadar BEKLE
      redis:
        condition: service_started  # Redis başlamış olsun yeter
      migrations:
        condition: service_completed_successfully  # Migration bitsin

  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]
      interval: 10s      # Her 10 saniyede kontrol et
      timeout: 5s         # 5 saniye içinde cevap gelmezse fail
      retries: 5          # 5 kez fail olursa unhealthy
      start_period: 30s   # İlk 30 saniye kontrol yapma

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5

  migrations:
    build: ./api
    command: ["node", "scripts/migrate.js"]
    depends_on:
      db:
        condition: service_healthy

Başlatma sırası: db → (healthy olunca) → redis + migrations → (migrations completed) → api. Her şey doğru sırada, doğru zamanda başlar.


Compose Override — Ortam Bazlı Yapılandırma

Aynı projeyi development ve production ortamlarında farklı ayarlarla çalıştırmak isteyeceksin. Compose bunu override dosyaları ile çözer.

# docker-compose.yml (base — her ortamda geçerli)
services:
  api:
    build: ./api
    environment:
      NODE_ENV: production

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:
# docker-compose.override.yml (development — OTOMATİK yüklenir!)
services:
  api:
    build:
      target: development
    volumes:
      - ./api/src:/app/src      # Hot reload
      - /app/node_modules
    ports:
      - "3000:3000"
      - "9229:9229"             # Debug port
    environment:
      NODE_ENV: development

  db:
    ports:
      - "5432:5432"             # Dev'de dışarıdan erişim

docker-compose.override.yml dosyası otomatik yüklenir. Yani docker compose up dediğinde hem base hem override uygulanır:

# Development (override otomatik)
docker compose up -d
# api: development stage, hot reload, debug port açık

# Production (override atlanır, prod dosyası kullanılır)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Ölçeklendirme

Bir servisten birden fazla instance çalıştırmak isteyebilirsin:

docker compose up -d --scale api=3

Bu, 3 tane API container'ı çalıştırır. Ama dikkat: eğer port mapping'de sabit host portu belirtmişsen çakışma olur. Dinamik port kullan:

services:
  api:
    ports:
      - "3000"  # Host portu Docker'a bırakılır

Resource Limitleri

Bir servisin tüm CPU veya RAM'i tüketmesini önlemek için kaynak limitleri koy:

services:
  api:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.5"
          memory: 256M

limits maksimum kullanım, reservations minimum garanti. API 512MB'dan fazla RAM kullanamaz ve en az 256MB garanti.


Logging Limitleri

Docker varsayılan olarak container loglarını sınırsız saklar. Zamanla disk dolabilir. Limit koy:

services:
  api:
    logging:
      driver: json-file
      options:
        max-size: "10m"    # Her log dosyası max 10MB
        max-file: "3"      # En fazla 3 dosya

Bu ayarla toplam log boyutu 30MB'ı geçmez.


Yaygın Hatalar

Hata 1 — Port çakışması:

docker compose up -d
# Error: port is already allocated

Başka bir container veya host servisi o portu kullanıyor. docker ps veya lsof -i :PORT ile kontrol et.

Hata 2 — depends_on ama servis hazır değil: Healthcheck olmadan depends_on sadece başlatma sırasını belirler, servisin hazır olduğunu garanti etmez. condition: service_healthy + healthcheck kullan.

Hata 3 — Volume izin sorunu: Container non-root user ile çalışıyor ama volume'a yazamıyor. Dockerfile'da RUN chown -R appuser:appuser /app/data ile izinleri ayarla.

Hata 4 — .env değerleri gelmiyor: Host'taki environment variable, .env dosyasından önce gelir. export DB_PASS=old varsa .env'deki yeni değer uygulanmaz. unset DB_PASS ile temizle.


Bu Derste Ne Öğrendik?

  • Docker Compose, çoklu container'ları tek YAML dosyasında tanımlar ve docker compose up ile başlatır.

  • docker-compose.yml'de services, volumes, networks tanımlanır.

  • depends_on + healthcheck ile servis başlatma sırası ve hazırlık kontrolü yapılır.

  • Override dosyaları ile development/production ortam ayrımı sağlanır — docker-compose.override.yml otomatik yüklenir.

  • Environment variable'ları .env dosyasında tut, git'e commit etme.

  • Resource limitleri ve logging limitleri koyarak kaynak kontrolü sağla.

Bir sonraki derste docker-compose.yml dosyasının anatomisini detaylıca inceleyeceğiz — services, networks, volumes bölümlerinin tüm seçenekleri ve production-ready konfigürasyon.