İçeriğe geç

Docker Compose: Çoklu Container Uygulamalarını Tek Komutla Yönetme Rehberi

T
Tolgahan
· · 33 dk okuma · 195 görüntülenme

Docker Compose: Çoklu Container Uygulamalarını Tek Komutla Yönetme Rehberi

Gerçek Hayattan Bir Sahne

Bir web uygulaması geliştiriyorsun diyelim. Backend için Spring Boot, veritabanı için PostgreSQL, cache için Redis, mesaj kuyruğu için RabbitMQ kullanıyorsun. Geliştirme ortamını ayağa kaldırmak için terminalde şunu yapıyorsun:

docker run -d --name postgres -e POSTGRES_PASSWORD=secret -p 5432:5432 postgres:16
docker run -d --name redis -p 6379:6379 redis:7-alpine
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
docker run -d --name app --link postgres --link redis --link rabbitmq -p 8080:8080 my-app

Dört ayrı komut. Her birinde port mapping, environment variable, link tanımı... Birini unutsan uygulama patlar. Sırayı karıştırsan bağlantı hatası alırsın. Takıma yeni biri katılsa, bu komutları tek tek açıklaman gerekir. Projeyi kapattığında hepsini ayrı ayrı durdurman, silmen lazım.

Şimdi aynı şeyi tek bir komutla yapmayı düşün:

docker compose up -d

Bitti. Tüm servisler ayağa kalktı, birbirleriyle konuşabiliyor, portlar açık, veriler kalıcı. İşte Docker Compose tam olarak bu problemi çözer.

Docker Compose Nedir?

🎯 Analoji: Docker Compose'u bir orkestra şefi gibi düşün. Her müzisyen (container) kendi enstrümanını çalıyor, ama şef hepsini koordine ediyor — kim ne zaman başlayacak, hangi ritimde çalacak, birbirlerini nasıl dinleyecek. docker-compose.yml dosyası da şefin elindeki nota kağıdı.

Docker Compose, birden fazla Docker container'ını tek bir YAML dosyasında tanımlayıp, tek bir komutla yönetmeni sağlayan bir araçtır. Docker'ın resmi bir parçasıdır ve docker compose komutuyla (v2) veya eski sürümlerde docker-compose komutuyla kullanılır.

Compose'un çözdüğü temel problemler şunlar:

  • Çoklu container orkestrasyonu — Birden fazla servisi birlikte tanımlama ve başlatma

  • Tekrarlanabilirlik — Aynı ortamı her seferinde aynı şekilde ayağa kaldırma

  • Dokümantasyon — YAML dosyası, altyapının canlı dokümantasyonu olarak hizmet eder

  • İzolasyon — Her proje kendi network'ünde, birbirini etkilemeden çalışır

Docker Compose özellikle şu senaryolarda hayat kurtarır: yerel geliştirme ortamları, entegrasyon testleri, demo ve staging ortamları, ve küçük-orta ölçekli production deploy'ları.

Temel Yapı: docker-compose.yml

Docker Compose'un kalbi docker-compose.yml (veya compose.yml — Docker Compose v2 her ikisini de tanır) dosyasıdır. Bu dosya YAML formatında yazılır ve uygulamanın tüm servislerini, network'lerini ve volume'larını tanımlar.

En basit haliyle bir Compose dosyası şöyle görünür:

# docker-compose.yml — En basit örnek
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"  # Host'un 8080 portunu container'ın 80 portuna yönlendir

Bu dosya tek bir şey söylüyor: "nginx:alpine image'ından bir container oluştur, 8080 portundan eriş." Basit, ama gücü çoklu servislerle ortaya çıkıyor.

Şimdi adım adım daha karmaşık bir yapıya geçelim.

İlk Gerçekçi Örnek: Spring Boot + PostgreSQL

Diyelim ki bir Spring Boot uygulaması geliştiriyorsun ve PostgreSQL veritabanı kullanıyorsun. İşte bu iki servisi Compose ile tanımlayalım:

# docker-compose.yml — Spring Boot + PostgreSQL
services:
  # PostgreSQL veritabanı servisi
  db:
    image: postgres:16-alpine
    container_name: myapp-db
    environment:
      POSTGRES_DB: myappdb          # Oluşturulacak veritabanı adı
      POSTGRES_USER: appuser        # Veritabanı kullanıcısı
      POSTGRES_PASSWORD: secretpass # Kullanıcı şifresi
    ports:
      - "5432:5432"                 # Dışarıdan erişim (geliştirme için)
    volumes:
      - postgres_data:/var/lib/postgresql/data  # Veri kalıcılığı
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d myappdb"]
      interval: 5s
      timeout: 5s
      retries: 5

  # Spring Boot uygulama servisi
  app:
    build:
      context: .                     # Dockerfile bu dizinde
      dockerfile: Dockerfile
    container_name: myapp-backend
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/myappdb
      SPRING_DATASOURCE_USERNAME: appuser
      SPRING_DATASOURCE_PASSWORD: secretpass
      SPRING_JPA_HIBERNATE_DDL_AUTO: update
    depends_on:
      db:
        condition: service_healthy   # db sağlıklı olana kadar bekle
    restart: unless-stopped

# Named volume tanımı
volumes:
  postgres_data:

Bu dosyada birkaç kritik detay var, hepsini tek tek açıklayalım.

services Bloğu

Her servis bir container'a karşılık gelir. db ve app birer servis adıdır — bu adlar aynı zamanda container'ların birbirini bulması için DNS ismi olarak da kullanılır. Yani app servisi, db servisine db hostname'iyle ulaşabilir. Bu yüzden SPRING_DATASOURCE_URL içinde localhost değil db yazıyoruz.

image vs build

db servisi hazır bir image kullanıyor (postgres:16-alpine). Ama app servisi bir Dockerfile'dan build ediliyor. build.context hangi dizindeki Dockerfile'ın kullanılacağını belirtir.

depends_on ile Servis Sıralaması

depends_on bir servisin başka bir servisten sonra başlamasını sağlar. Ama dikkat: sadece depends_on: [db] yazarsan, Docker Compose sadece container'ın başlatıldığını garanti eder — veritabanının hazır olduğunu değil. PostgreSQL container'ı başlatılmış olabilir ama henüz bağlantı kabul etmiyor olabilir.

Bu yüzden condition: service_healthy ile health check'e bağlıyoruz. db servisinin healthcheck tanımı sayesinde Compose, PostgreSQL gerçekten bağlantı kabul edene kadar app servisini başlatmaz.

volumes ile Veri Kalıcılığı

Container'lar doğası gereği geçicidir (ephemeral). Container'ı sildiğinde içindeki tüm veri kaybolur. Veritabanı için bu felaket demek. volumes ile container'ın verilerini host'ta kalıcı olarak saklıyoruz.

İki tür volume var:

  • Named volume (postgres_data:/var/lib/postgresql/data) — Docker yönetir, taşınabilir, temiz

  • Bind mount (./data:/var/lib/postgresql/data) — Host dizinini doğrudan bağlar, geliştirmede pratik

Production'da named volume tercih edilir. Geliştirmede kaynak kodu bind mount ile bağlamak yaygındır.

Tam Bir Mikroservis Ortamı

Şimdi daha gerçekçi bir senaryo kuralım. Bir e-ticaret uygulamasının geliştirme ortamını düşün: Spring Boot backend, PostgreSQL veritabanı, Redis cache ve Nginx reverse proxy.

# docker-compose.yml — E-Ticaret Geliştirme Ortamı
services:

  # === Veritabanı Katmanı ===
  postgres:
    image: postgres:16-alpine
    container_name: ecommerce-db
    environment:
      POSTGRES_DB: ecommerce
      POSTGRES_USER: ecom_user
      POSTGRES_PASSWORD: ${DB_PASSWORD:-defaultpass}  # .env'den oku
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./docker/init-scripts:/docker-entrypoint-initdb.d  # Başlangıç SQL'leri
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ecom_user -d ecommerce"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - backend-net

  # === Cache Katmanı ===
  redis:
    image: redis:7-alpine
    container_name: ecommerce-cache
    command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - backend-net

  # === Uygulama Katmanı ===
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        JAR_FILE: target/*.jar
    container_name: ecommerce-app
    environment:
      SPRING_PROFILES_ACTIVE: docker
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/ecommerce
      SPRING_DATASOURCE_USERNAME: ecom_user
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-defaultpass}
      SPRING_DATA_REDIS_HOST: redis
      SPRING_DATA_REDIS_PORT: 6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - backend-net
      - frontend-net
    restart: unless-stopped

  # === Reverse Proxy Katmanı ===
  nginx:
    image: nginx:alpine
    container_name: ecommerce-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro  # Read-only
      - ./docker/nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - app
    networks:
      - frontend-net
    restart: unless-stopped

# Named volume'lar
volumes:
  pg_data:
  redis_data:

# Custom network'ler
networks:
  backend-net:
    driver: bridge
  frontend-net:
    driver: bridge

Bu yapıda dikkat edilmesi gereken birkaç önemli nokta var.

Network İzolasyonu

İki ayrı network tanımladık: backend-net ve frontend-net. Nginx sadece frontend-net'te, PostgreSQL ve Redis sadece backend-net'te. app servisi her iki ağda da çünkü hem veritabanına bağlanması hem de Nginx'ten istek alması gerekiyor.

Bu izolasyon güvenlik için kritiktir. Nginx container'ı doğrudan veritabanına erişemez — erişmesine gerek de yok. Her servis sadece ihtiyacı olan ağlara dahil olur.

Environment Variable Yönetimi: .env Dosyası

Compose dosyasında ${DB_PASSWORD:-defaultpass} ifadesi bir environment variable referansıdır. Docker Compose, aynı dizindeki .env dosyasını otomatik olarak okur:

# .env — Compose ile aynı dizinde
DB_PASSWORD=guclu_bir_sifre_123
REDIS_MAX_MEMORY=256mb
APP_PORT=8080

:-defaultpass kısmı default değer belirtir. .env dosyasında DB_PASSWORD tanımlı değilse defaultpass kullanılır. Bu sayede takıma yeni katılan biri .env dosyası oluşturmadan da projeyi çalıştırabilir.

⚠️ Dikkat: .env dosyasını kesinlikle .gitignore'a ekle. Şifreleri Git'e commitlememek bir güvenlik kuralıdır. Bunun yerine bir .env.example dosyası oluştur ve gerekli değişkenleri boş haliyle paylaş.

Custom Command ile Konfigürasyon

Redis servisinde command alanıyla container'ın varsayılan başlatma komutunu değiştirdik. redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru komutu Redis'e en fazla 128MB bellek kullanmasını ve bellek dolduğunda en az kullanılan anahtarları silmesini söylüyor.

Bu teknik, özel konfigürasyon dosyası yazmadan servisleri yapılandırmak için çok kullanışlıdır.

Init Scripts ile Veritabanı Hazırlığı

PostgreSQL image'ı /docker-entrypoint-initdb.d dizinindeki .sql ve .sh dosyalarını ilk başlatmada otomatik çalıştırır. Bu dizine tablo oluşturma, seed data ekleme veya extension kurma script'leri koyabilirsin:

-- docker/init-scripts/01-create-tables.sql
CREATE TABLE IF NOT EXISTS products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock INTEGER DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS orders (
    id SERIAL PRIMARY KEY,
    product_id INTEGER REFERENCES products(id),
    quantity INTEGER NOT NULL,
    total_price DECIMAL(10,2) NOT NULL,
    status VARCHAR(50) DEFAULT 'PENDING',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Başlangıç verisi
INSERT INTO products (name, price, stock) VALUES
    ('Mekanik Klavye', 1499.99, 50),
    ('Kablosuz Mouse', 699.99, 100),
    ('27" Monitor', 8999.99, 25);

Dosya adındaki 01- prefix'i çalışma sırasını belirler. 01-create-tables.sql her zaman 02-seed-data.sql'den önce çalışır.

Temel Docker Compose Komutları

Docker Compose'u günlük geliştirmede kullanırken ihtiyacın olan komutları tanıyalım:

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

# Tüm servisleri başlat ve image'ları yeniden build et
docker compose up -d --build

# Belirli bir servisi başlat
docker compose up -d postgres redis

# Tüm servisleri durdur
docker compose down

# Servisleri durdur VE volume'ları sil (veritabanı dahil!)
docker compose down -v

# Çalışan servislerin durumunu gör
docker compose ps

# Bir servisin loglarını izle
docker compose logs -f app

# Birden fazla servisin loglarını izle
docker compose logs -f app postgres

# Çalışan bir container'a shell aç
docker compose exec postgres psql -U ecom_user -d ecommerce

# Bir servisi yeniden başlat
docker compose restart app

# Sadece bir servisi durdur
docker compose stop redis

# Servisleri ölçeklendir (aynı servisten birden fazla instance)
docker compose up -d --scale app=3

💡 İpucu: docker compose up -d --build komutu günlük geliştirmede en çok kullanacağın komuttur. Kod değişikliği yaptıktan sonra image'ı yeniden build edip servisleri günceller. Sadece değişen katmanlar yeniden build edildiği için genellikle hızlıdır.

Geliştirme vs Production: Compose Override

Docker Compose'un güçlü bir özelliği, birden fazla Compose dosyasını birleştirmesidir (merge). Bu sayede geliştirme ve production ortamları için farklı konfigürasyonlar tanımlayabilirsin.

Temel yapılandırmayı docker-compose.yml'de, geliştirmeye özel ayarları docker-compose.override.yml'de tutarsın. Docker Compose, docker compose up komutunda her iki dosyayı da otomatik olarak okur ve birleştirir.

# docker-compose.yml — Temel (production-ready) yapılandırma
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      SPRING_PROFILES_ACTIVE: production
    restart: always
    networks:
      - app-net

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data
    networks:
      - app-net
    # Production'da dışarıya port AÇMA

volumes:
  pg_data:

networks:
  app-net:
# docker-compose.override.yml — Geliştirme ortamına özel
services:
  app:
    build:
      target: development            # Multi-stage build'de development stage
    environment:
      SPRING_PROFILES_ACTIVE: dev    # Override: production → dev
      DEBUG: "true"
    ports:
      - "8080:8080"                  # Geliştirmede dışarıya aç
      - "5005:5005"                  # Java debug portu
    volumes:
      - ./src:/app/src               # Hot-reload için kaynak kodu bağla

  postgres:
    ports:
      - "5432:5432"                  # Geliştirmede DB'ye dışarıdan eriş
    environment:
      POSTGRES_PASSWORD: devpassword # Geliştirmede basit şifre

  # Geliştirmede ekstra araçlar
  adminer:
    image: adminer:latest
    ports:
      - "9090:8080"
    networks:
      - app-net

Bu yaklaşımın güzelliği şu: docker compose up dediğinde override dosyası otomatik birleşir ve geliştirme ortamını alırsın. Production'da ise sadece temel dosyayı kullanırsın:

# Geliştirme (override otomatik uygulanır)
docker compose up -d

# Production (sadece temel dosya + production dosyası)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Health Check Stratejileri

Servisler arası bağımlılıkları yönetirken health check'ler kritik rol oynar. Her veritabanı ve araç için uygun health check tanımı farklıdır:

services:
  # PostgreSQL — pg_isready komutuyla kontrol
  postgres:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 5s    # Her 5 saniyede bir kontrol et
      timeout: 5s     # 5 saniye içinde cevap gelmezse başarısız say
      retries: 5      # 5 başarısız denemeden sonra "unhealthy" işaretle
      start_period: 10s  # İlk 10 saniye kontrol etme (başlatma süresi)

  # MySQL — mysqladmin ile kontrol
  mysql:
    image: mysql:8
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 5s
      retries: 5

  # Redis — redis-cli ping ile kontrol
  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  # Elasticsearch — HTTP endpoint ile kontrol
  elasticsearch:
    image: elasticsearch:8.12.0
    healthcheck:
      test: ["CMD-SHELL", "curl -fsSL http://localhost:9200/_cluster/health || exit 1"]
      interval: 10s
      timeout: 10s
      retries: 10
      start_period: 30s  # ES yavaş başlar, 30 sn bekle

  # Uygulama — Spring Boot Actuator health endpoint
  app:
    build: .
    healthcheck:
      test: ["CMD-SHELL", "curl -fsSL http://localhost:8080/actuator/health || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 40s  # Spring Boot uygulaması başlayana kadar bekle
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

start_period özellikle önemlidir. Spring Boot uygulamaları veya Elasticsearch gibi yavaş başlayan servisler için bu süreyi yeterince uzun tutmalısın. Aksi halde servis henüz başlamadan "unhealthy" damgasını yer ve yeniden başlatılır — sonsuz döngüye girebilir.

⚠️ Dikkat: $$ (çift dolar) YAML'da environment variable'ı Docker Compose'un değil, container'ın kendisinin çözmesini sağlar. $POSTGRES_USER yazarsan Compose bunu host'taki environment'ta arar (ve bulamazsa boş bırakır). $$POSTGRES_USER yazarsan container içindeki değişken kullanılır.

Yaygın Hatalar ve Tuzaklar

Docker Compose kullanırken neredeyse herkesin düştüğü tuzaklar var. Bunları bilmek saatlerce debug süresinden kurtarır.

1. depends_on Tek Başına Yetmez

En yaygın hata: depends_on: [postgres] yazıp veritabanının hazır olduğunu varsaymak.

# ❌ YANLIŞ — Sadece container'ın başlatıldığını garanti eder
app:
  depends_on:
    - postgres

# ✅ DOĞRU — Veritabanının gerçekten hazır olduğunu garanti eder
app:
  depends_on:
    postgres:
      condition: service_healthy

depends_on liste formatında sadece başlatma sırasını garanti eder. PostgreSQL container'ı başlatılmış olabilir ama henüz bağlantı kabul etmiyor olabilir. Spring Boot uygulaması bağlanmaya çalışır, "Connection refused" alır ve çöker. Bu hatayı bulmak zorlaşır çünkü bazen PostgreSQL hızlı başlar ve sorun oluşmaz — ama CI/CD'de veya yavaş bir makinede patlar.

2. Volume Silmeden Veritabanı Değişikliği Yapmak

PostgreSQL init script'leri sadece ilk başlatmada çalışır. Volume zaten varsa script'ler atlanır.

# Init script'ini güncelledin ama çalışmıyor?
# Çünkü volume zaten mevcut veritabanı verisini içeriyor.

# Volume'u silip sıfırdan başlatman gerekir:
docker compose down -v    # Volume'ları da sil
docker compose up -d      # Yeniden oluştur

Bu yüzden schema değişikliklerini init script yerine migration araçlarıyla (Flyway, Liquibase) yönetmek daha sağlıklıdır.

3. localhost Yerine Servis Adı Kullanmamak

Container'lar kendi izole network'lerinde çalışır. Bir container içinden localhost o container'ın kendisine işaret eder, host makineye değil.

# ❌ YANLIŞ — Container içinden host'a erişemez
environment:
  SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/mydb

# ✅ DOĞRU — Docker DNS ile servis adını çözer
environment:
  SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/mydb

Docker Compose, aynı network'teki servislerin birbirini servis adıyla (YAML'daki anahtar) bulmasını sağlayan dahili bir DNS sunucusu çalıştırır.

4. .env Dosyasındaki Özel Karakterler

.env dosyasında şifrelerde $, #, ! gibi özel karakterler sorun çıkarabilir:

# ❌ SORUNLU — $ işareti variable interpolation tetikler
DB_PASSWORD=p@$$w0rd!

# ✅ DOĞRU — Tek tırnak ile özel karakterleri koru
DB_PASSWORD='p@$$w0rd!'

5. Build Cache Sorunları

Dockerfile'da değişiklik yaptın ama container eski kodu çalıştırıyor? Docker build cache'i eski katmanları kullanıyor olabilir:

# Cache'i atlayarak sıfırdan build et
docker compose build --no-cache

# Veya up komutuyla birlikte
docker compose up -d --build --force-recreate

Best Practices: Profesyonel Compose Kullanımı

1. Her Zaman Explicit Image Tag Kullan

# ❌ YANLIŞ — "latest" her pull'da farklı versiyon getirebilir
image: postgres:latest

# ✅ DOĞRU — Sabit versiyon, tekrarlanabilir ortam
image: postgres:16.2-alpine

latest etiketi bir sabiti değil, "en son yayınlanan"ı ifade eder. Bugün çalışan docker compose up, yarın farklı bir PostgreSQL versiyonuyla ayağa kalkabilir ve uyumsuzluk yaşarsın. Alpine varyantları daha küçük image boyutu sağlar.

2. Resource Limitleri Belirle

Production'da container'ların sınırsız kaynak tüketmesini önle:

services:
  app:
    image: my-app:1.0
    deploy:
      resources:
        limits:
          cpus: "2.0"       # Max 2 CPU core
          memory: 1G         # Max 1 GB RAM
        reservations:
          cpus: "0.5"       # Min 0.5 CPU core garanti
          memory: 256M       # Min 256 MB RAM garanti

3. Restart Policy Tanımla

services:
  app:
    restart: unless-stopped  # Manuel durdurulmadıkça her zaman yeniden başlat
  
  worker:
    restart: on-failure      # Sadece hata ile çıkarsa yeniden başlat
    # restart: "no"          # Hiçbir zaman yeniden başlatma (varsayılan)
    # restart: always        # Her durumda yeniden başlat

Geliştirmede unless-stopped, production'da always veya on-failure tercih edilir. no genellikle tek seferlik görevler (migration, seed) için kullanılır.

4. Logging Konfigürasyonu

Container logları disk alanını tüketebilir. Log boyutunu sınırla:

services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"      # Her log dosyası max 10 MB
        max-file: "3"        # En fazla 3 dosya tut (toplam max 30 MB)

5. Read-Only Bind Mount'lar

Konfigürasyon dosyalarını bind mount ederken, container'ın bunları değiştirmesini istemiyorsan :ro (read-only) flag'ini kullan:

volumes:
  - ./config/nginx.conf:/etc/nginx/nginx.conf:ro
  - ./config/ssl:/etc/nginx/ssl:ro

Bu hem güvenlik hem de netlik sağlar — konfigürasyon sadece host'tan yönetilir.

Gerçek Dünya Örneği: Komple Geliştirme Ortamı

Tüm öğrendiklerimizi bir araya getiren, gerçek bir projede kullanabileceğin komple bir geliştirme ortamı kuralım. Bu örnekte bir blog platformu geliştiriyoruz:

# docker-compose.yml — Blog Platformu Geliştirme Ortamı
# Kullanım: docker compose up -d

services:
  # ─── Veritabanı ───
  postgres:
    image: postgres:16.2-alpine
    container_name: blog-db
    environment:
      POSTGRES_DB: blogdb
      POSTGRES_USER: bloguser
      POSTGRES_PASSWORD: ${DB_PASSWORD:-dev_password_123}
      # Türkçe karakter desteği
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.utf8"
    ports:
      - "${DB_PORT:-5432}:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./docker/postgres/init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U bloguser -d blogdb"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - db-net
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  # ─── Cache ───
  redis:
    image: redis:7.2-alpine
    container_name: blog-cache
    command: >
      redis-server
      --maxmemory 128mb
      --maxmemory-policy allkeys-lru
      --appendonly yes
    ports:
      - "${REDIS_PORT:-6379}:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - db-net

  # ─── Backend API ───
  api:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: blog-api
    environment:
      SPRING_PROFILES_ACTIVE: docker
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/blogdb
      SPRING_DATASOURCE_USERNAME: bloguser
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-dev_password_123}
      SPRING_DATA_REDIS_HOST: redis
      SPRING_DATA_REDIS_PORT: 6379
      SERVER_PORT: 8080
    ports:
      - "8080:8080"
      - "5005:5005"     # Remote debug
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - db-net
      - web-net
    restart: unless-stopped
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  # ─── Mail Sunucusu (Geliştirme) ───
  mailhog:
    image: mailhog/mailhog:latest
    container_name: blog-mail
    ports:
      - "1025:1025"     # SMTP
      - "8025:8025"     # Web UI — Gönderilen e-postaları görmek için
    networks:
      - db-net

  # ─── Veritabanı Yönetim Aracı ───
  pgadmin:
    image: dpage/pgadmin4:8
    container_name: blog-pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@local.dev
      PGADMIN_DEFAULT_PASSWORD: admin
      PGADMIN_CONFIG_SERVER_MODE: "False"
    ports:
      - "5050:80"
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - db-net

volumes:
  pg_data:
    name: blog-pg-data
  redis_data:
    name: blog-redis-data

networks:
  db-net:
    name: blog-db-network
    driver: bridge
  web-net:
    name: blog-web-network
    driver: bridge

Bu yapıda her şey düşünülmüş durumda:

  • PostgreSQL UTF-8 encoding ile Türkçe karakter desteği sağlıyor

  • Redis AOF (Append Only File) persistence ile veri kaybını önlüyor

  • API servisi hem veritabanı hem cache'e bağlı, health check ile sıralı başlatılıyor

  • MailHog geliştirmede e-posta testlerini gerçek bir SMTP sunucusu olmadan yapmanı sağlıyor — localhost:8025'ten gönderilen tüm e-postaları görebilirsin

  • pgAdmin veritabanını tarayıcıdan yönetmek için hazır

  • Her servisin log boyutu sınırlı, volume'lar isimlendirilmiş, network'ler izole

Bu ortamı ayağa kaldırmak tek bir komut:

docker compose up -d

Ardından:

  • API: http://localhost:8080

  • pgAdmin: http://localhost:5050

  • MailHog: http://localhost:8025

Tüm ortamı kapatmak:

# Sadece durdur (verileri koru)
docker compose down

# Tamamen sil (veritabanı dahil)
docker compose down -v --remove-orphans

Compose ile Debugging İpuçları

Bir servis beklediğin gibi çalışmadığında, sorunun kaynağını bulmak için şu teknikleri kullan:

# Servis loglarını canlı izle
docker compose logs -f api

# Son 50 satır logu gör
docker compose logs --tail=50 postgres

# Container'a shell ile gir ve içerde debug et
docker compose exec api sh

# Bir container'ın environment variable'larını gör
docker compose exec api env

# Bir container'ın network ayarlarını kontrol et
docker compose exec api cat /etc/hosts

# DNS çözümlemesini test et
docker compose exec api ping postgres

# Compose konfigürasyonunu doğrula (syntax hatalarını yakala)
docker compose config

# Compose konfigürasyonunu birleştirilmiş haliyle gör
docker compose config --format json

docker compose config komutu, YAML dosyandaki syntax hatalarını yakalamak için çok değerlidir. Compose dosyasını değiştirdikten sonra up çalıştırmadan önce config ile doğrulamayı alışkanlık edinmek, saatlerce debug süresinden kurtarır.

💡 İpucu: docker compose exec çalışan bir container'a bağlanır, docker compose run ise yeni bir container oluşturur. Debugging için exec tercih et — çalışan container'ın durumunu görmek istiyorsun, yeni bir tane başlatmak değil.

Docker Compose vs Kubernetes: Ne Zaman Hangisi?

Docker Compose harika bir araç ama her senaryo için uygun değil. Sınırlarını bilmek, doğru aracı seçmeni sağlar.

Docker Compose tercih et:

  • Yerel geliştirme ortamları

  • CI/CD pipeline'larında test ortamı

  • Tek sunucuda çalışan küçük-orta ölçekli uygulamalar

  • Demo ve staging ortamları

  • Hızlı prototipleme

Kubernetes'e geç:

  • Birden fazla sunucuda dağıtık çalışma

  • Otomatik ölçeklendirme (auto-scaling) gereksinimi

  • Sıfır kesinti deploy (zero-downtime deployment) zorunluluğu

  • Çok sayıda mikroservisin yönetimi (onlarca servis)

  • Self-healing ve gelişmiş orkestrasyon

Docker Compose, Kubernetes öğrenmeden önce container orkestrasyonunun temellerini anlamak için mükemmel bir başlangıç noktasıdır. Compose'da öğrendiğin servis tanımları, network izolasyonu ve volume yönetimi kavramları Kubernetes'e geçişte doğrudan işine yarar.

Sonuç

Docker Compose, modern yazılım geliştirmenin olmazsa olmazlarından biri. Öğrendiklerimizi özetleyelim:

  • `docker-compose.yml` dosyası, uygulamanın tüm altyapısını tanımlayan canlı bir dokümantasyondur

  • Services her bir container'ı, networks iletişim kanallarını, volumes kalıcı veriyi tanımlar

  • `depends_on` + `condition: service_healthy` kombinasyonu, servislerin doğru sırada ve hazır olduklarında başlamasını garanti eder

  • Environment variable'lar .env dosyasıyla yönetilir ve hassas veriler Git'e eklenmez

  • Override dosyaları (docker-compose.override.yml) geliştirme ve production ortamlarını aynı temel üzerinde farklılaştırır

  • Network izolasyonu ile her servis sadece ihtiyacı olan servislere erişir

Bir sonraki adım olarak, kendi projende Docker Compose dosyası oluşturmayı dene. Küçük başla — uygulama + veritabanı yeterli. Sonra Redis ekle, Nginx ekle, monitoring araçları ekle. Her adımda Compose'un gücünü biraz daha keşfedeceksin.

Ve unutma: docker compose up -d her şeyi ayağa kaldırır, docker compose down her şeyi indirir. Bu kadar basit olması, bu kadar güçlü bir aracın en güzel yanı.

Paylaş:
Son güncelleme: Apr 21, 2026

Yorumlar

Giriş yapın ve yorum bırakın.

Henüz yorum yok

Düşüncelerinizi paylaşan ilk siz olun!

Bu yazıyı beğendiniz mi?

Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.

Bu konuyu derinlemesine öğrenmek ister misin?

Docker: Sıfırdan Uzmanlığa

60 ders Ücretsiz
Kursa Git

İlgili Yazılar