← Kursa Dön
📄 Text · 30 min

Docker Compose

Giriş

Gerçek dünya uygulamaları tek bir container'dan oluşmaz. Bir Spring Boot uygulaması genellikle PostgreSQL veritabanı, Redis cache, RabbitMQ mesaj kuyruğu ve belki bir Elasticsearch instance'ı ile birlikte çalışır. Bu servisleri tek tek docker run komutuyla yönetmek zahmetlidir — her servis için port, network, volume, environment variable belirtmeniz gerekir. 5 servis için 5 ayrı uzun komut yazmak ve bunların doğru sırada başlatılmasını sağlamak kabus gibidir.

Bunu bir orkestra şefi gibi düşünün: her müzisyen kendi notasını çalabilir, ama şef olmadan uyum yoktur. Docker Compose, container'larınızın orkestra şefidir — birden fazla container'ı tek bir YAML dosyasıyla tanımlayıp, tek bir komutla yönetmenizi sağlar.

Bu derste Docker Compose'un yapısını, Spring Boot ile tam bir geliştirme ortamı kurulumunu, network ve volume yönetimini, health check'ler ve depends_on ile servis orkestrasyonunu derinlemesine inceleyeceğiz.

Docker Compose Nedir?

Docker Compose, çok container'lı uygulamaları tanımlamak ve çalıştırmak için bir araçtır. compose.yml (veya docker-compose.yml) dosyasında tüm servisleri, ağları ve volume'ları tanımlarsınız:

# Docker Compose olmadan — 5 ayrı komut
docker network create myapp-net

docker run -d --name mydb --network myapp-net \
    -e POSTGRES_DB=mydb -e POSTGRES_PASSWORD=secret \
    -v pgdata:/var/lib/postgresql/data \
    postgres:16-alpine

docker run -d --name myredis --network myapp-net \
    -v redisdata:/data \
    redis:7-alpine

docker run -d --name myrabbitmq --network myapp-net \
    -p 15672:15672 \
    rabbitmq:3-management-alpine

docker run -d --name myapp --network myapp-net \
    -p 8080:8080 \
    -e SPRING_DATASOURCE_URL=jdbc:postgresql://mydb:5432/mydb \
    -e SPRING_REDIS_HOST=myredis \
    myapp:latest

# Docker Compose ile — tek komut
docker compose up -d

YAML Yapısı

compose.yml dosyasının temel yapısı:

# Top-level elemanlar
services:     # Container tanımları (zorunlu)
  app:
    # ...
  db:
    # ...

networks:     # Ağ tanımları (opsiyonel)
  app-net:
    # ...

volumes:      # Kalıcı veri tanımları (opsiyonel)
  db-data:
    # ...

configs:      # Konfigürasyon dosyaları (opsiyonel)
secrets:      # Hassas bilgiler (opsiyonel)

Tam Örnek: Spring Boot + PostgreSQL + Redis + RabbitMQ

services:
  # ===== Spring Boot Uygulaması =====
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: order-service
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: docker
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/orderdb
      SPRING_DATASOURCE_USERNAME: postgres
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-secret}
      SPRING_DATA_REDIS_HOST: redis
      SPRING_DATA_REDIS_PORT: 6379
      SPRING_RABBITMQ_HOST: rabbitmq
      SPRING_RABBITMQ_USERNAME: guest
      SPRING_RABBITMQ_PASSWORD: guest
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
      rabbitmq:
        condition: service_healthy
    networks:
      - app-net
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 3

  # ===== PostgreSQL Veritabanı =====
  db:
    image: postgres:16-alpine
    container_name: order-db
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: orderdb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=en_US.UTF-8"
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d  # Başlangıç SQL'leri
    networks:
      - app-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d orderdb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped

  # ===== Redis Cache =====
  redis:
    image: redis:7-alpine
    container_name: order-redis
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    networks:
      - app-net
    restart: unless-stopped

  # ===== RabbitMQ Mesaj Kuyruğu =====
  rabbitmq:
    image: rabbitmq:3-management-alpine
    container_name: order-rabbitmq
    ports:
      - "5672:5672"     # AMQP
      - "15672:15672"   # Management UI
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
    volumes:
      - rabbitmq-data:/var/lib/rabbitmq
    networks:
      - app-net
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
      interval: 30s
      timeout: 10s
      retries: 5
    restart: unless-stopped

  # ===== pgAdmin (Veritabanı Yönetimi) =====
  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: order-pgadmin
    ports:
      - "5050:80"
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: admin
    networks:
      - app-net
    restart: unless-stopped
    profiles:
      - debug  # Sadece --profile debug ile başlatılır

# ===== Networks =====
networks:
  app-net:
    driver: bridge

# ===== Volumes =====
volumes:
  db-data:
    driver: local
  redis-data:
    driver: local
  rabbitmq-data:
    driver: local

Services — Detaylı Açıklama

build

services:
  app:
    # Basit — Dockerfile kök dizinde
    build: .

    # Detaylı — özel Dockerfile, build args
    build:
      context: .                    # Build context dizini
      dockerfile: Dockerfile.prod   # Özel Dockerfile adı
      args:
        JAR_FILE: target/myapp.jar  # Build argument
        APP_VERSION: "1.0.0"
      target: runtime               # Multi-stage'de hedef stage

ports

ports:
  - "8080:8080"       # host:container — dışarıdan erişilebilir
  - "127.0.0.1:9090:9090"  # Sadece localhost'tan erişilebilir

environment vs env_file

# Inline environment variables
environment:
  SPRING_PROFILES_ACTIVE: docker
  DATABASE_URL: jdbc:postgresql://db:5432/mydb
  DB_PASSWORD: ${DB_PASSWORD}  # .env dosyasından

# Dosyadan environment variables
env_file:
  - .env
  - .env.local  # override
# .env dosyası (Git'e eklenmemeli!)
DB_PASSWORD=supersecret
JWT_SECRET=myLongRandomSecret
SPRING_PROFILES_ACTIVE=docker

volumes

volumes:
  # Named volume — Docker yönetir, container silinse bile verisi kalır
  - db-data:/var/lib/postgresql/data

  # Bind mount — host dosya sisteminden
  - ./init-scripts:/docker-entrypoint-initdb.d  # SQL dosyaları
  - ./logs:/app/logs                             # Log dosyaları

  # Read-only bind mount
  - ./config:/app/config:ro
Named Volume vs Bind Mount:

Named Volume:
✅ Docker yönetir, taşınabilir
✅ Performans daha iyi (Linux'ta)
✅ docker volume ls ile listelenir
❌ Host'tan doğrudan erişim zor

Bind Mount:
✅ Host'tan doğrudan erişim
✅ Geliştirme sırasında kaynak kodu paylaşma
❌ Host-specific yol gerekir
❌ Performans macOS/Windows'da düşük

Networks — Servisler Arası İletişim

Docker Compose, her proje için otomatik olarak bir bridge network oluşturur. Aynı ağdaki container'lar birbirlerine servis adı ile erişir:

# "app" servisi, "db" servisine "db" hostname'i ile erişir
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/orderdb
#                                        ^^
#                               servis adı = hostname

Network İzolasyonu

services:
  # Frontend (API Gateway) — hem dış hem iç ağda
  gateway:
    networks:
      - frontend
      - backend

  # Backend servisler — sadece iç ağda
  app:
    networks:
      - backend

  # Veritabanı — sadece iç ağda (dışarıdan erişilemez)
  db:
    networks:
      - backend

networks:
  frontend:  # Dışarıya açık
  backend:   # İç ağ — dışarıdan erişilemez

Bu yapıda db servisi sadece backend ağında — dışarıdan doğrudan erişilemez. Güvenlik için önemlidir.

depends_on ve Health Checks

depends_on, servislerin başlatılma sırasını belirler. Ancak bir servisin başlaması, hazır olduğu anlamına gelmez — PostgreSQL container'ı ayağa kalkabilir ama henüz bağlantı kabul etmiyordur.

services:
  app:
    depends_on:
      db:
        condition: service_healthy   # DB health check geçene kadar bekle
      redis:
        condition: service_started   # Redis başlatılınca yeter
      rabbitmq:
        condition: service_healthy   # RabbitMQ hazır olana kadar bekle

  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

Health Check Durumları

starting  → Container başlatılıyor, health check henüz çalışmadı
healthy   → Health check başarılı
unhealthy → Ardışık retries kadar başarısız

⚠️ Dikkat: condition: service_healthy olmadan depends_on sadece container'ın başlatılmasını bekler, hazır olmasını değil. Spring Boot uygulamanız veritabanı hazır olmadan önce başlarsa connection refused hatası alır.

Profiller (Profiles)

Bazı servisleri sadece belirli senaryolarda başlatmak için profiller kullanın:

services:
  app:
    # Her zaman başlar
    build: .

  db:
    # Her zaman başlar
    image: postgres:16-alpine

  pgadmin:
    image: dpage/pgadmin4
    profiles:
      - debug   # Sadece debug profilinde

  prometheus:
    image: prom/prometheus
    profiles:
      - monitoring   # Sadece monitoring profilinde

  grafana:
    image: grafana/grafana
    profiles:
      - monitoring
# Sadece temel servisler
docker compose up -d

# Debug araçlarıyla
docker compose --profile debug up -d

# Monitoring ile
docker compose --profile monitoring up -d

# Hepsi birden
docker compose --profile debug --profile monitoring up -d

Scaling — Çoklu Instance

# app servisinden 3 instance başlat
docker compose up -d --scale app=3

# Dikkat: ports'ta sabit mapping varsa çakışır
# "8080:8080" → 3 instance aynı portu kullanamaz!
# Scaling uyumlu port konfigürasyonu
services:
  app:
    build: .
    # ports: TANIMLAMAYIN — Nginx/Gateway üzerinden erişin
    expose:
      - "8080"  # Sadece iç ağda erişilebilir

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app

Docker Compose Komutları

# ===== Başlatma =====
docker compose up -d              # Tüm servisleri başlat (detached)
docker compose up -d --build      # Yeniden build et ve başlat
docker compose up -d app          # Sadece app servisini başlat

# ===== Loglama =====
docker compose logs -f            # Tüm logları takip et
docker compose logs -f app        # Sadece app logları
docker compose logs --tail=100 db # Son 100 satır

# ===== Durdurma =====
docker compose down               # Servisleri durdur, container'ları sil
docker compose down -v            # + Volume'ları da sil (DİKKAT!)
docker compose stop               # Durdur ama container'ları silme
docker compose start              # Durdurulmuş servisleri yeniden başlat

# ===== Build =====
docker compose build              # Tüm servisleri build et
docker compose build app          # Sadece app'i build et
docker compose build --no-cache   # Cache'siz build

# ===== Durum =====
docker compose ps                 # Servis durumlarını göster
docker compose top                # Container process'lerini göster

# ===== Shell =====
docker compose exec app sh        # app container'ına shell aç
docker compose exec db psql -U postgres  # DB'ye bağlan

# ===== Temizlik =====
docker compose down --rmi all -v  # Her şeyi sil (image + volume)
docker system prune -a            # Kullanılmayan tüm kaynakları temizle

Geliştirme Ortamı: Live Reload

# docker-compose.dev.yml — development override
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - ./src:/app/src:ro            # Kaynak kodu paylaş
      - ./target:/app/target         # Build çıktısı paylaş
    environment:
      SPRING_DEVTOOLS_RESTART_ENABLED: "true"
      SPRING_PROFILES_ACTIVE: dev
    ports:
      - "8080:8080"
      - "5005:5005"  # Remote debug portu
    command: >
      java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
      -jar app.jar
# Development modunda çalıştır
docker compose -f compose.yml -f docker-compose.dev.yml up -d

Production vs Development Compose

Farklı ortamlar için farklı compose dosyaları kullanın:

# compose.yml — temel yapı (her ortamda ortak)
services:
  app:
    image: myapp:${APP_VERSION:-latest}
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/mydb

  db:
    image: postgres:16-alpine
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:
# compose.dev.yml — development override
services:
  app:
    build: .          # image yerine build
    ports:
      - "8080:8080"
      - "5005:5005"   # debug port
    environment:
      SPRING_PROFILES_ACTIVE: dev
      JAVA_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
    volumes:
      - ./src:/app/src:ro

  db:
    ports:
      - "5432:5432"   # dışarıdan erişim (dev için)
# compose.prod.yml — production override
services:
  app:
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 1g
          cpus: "1.0"
    environment:
      SPRING_PROFILES_ACTIVE: prod
    # ports YOK — reverse proxy üzerinden

  db:
    # ports YOK — dışarıdan erişim kapalı
    deploy:
      resources:
        limits:
          memory: 2g
# Development
docker compose -f compose.yml -f compose.dev.yml up -d

# Production
docker compose -f compose.yml -f compose.prod.yml up -d

Watch Mode (Docker Compose Watch)

Docker Compose 2.22+ ile kaynak kodu değişikliklerini otomatik algılama:

services:
  app:
    build: .
    develop:
      watch:
        # Java dosyası değiştiğinde → rebuild & restart
        - action: rebuild
          path: ./src
          target: /app/src

        # application.yml değiştiğinde → sync + restart
        - action: sync+restart
          path: ./src/main/resources/application.yml
          target: /app/config/application.yml

        # static dosyalar değiştiğinde → sadece sync (restart yok)
        - action: sync
          path: ./src/main/resources/static
          target: /app/static
# Watch modunda başlat
docker compose watch

Monitoring Stack Compose Örneği

# compose.monitoring.yml
services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    networks:
      - app-net
    profiles:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - grafana-data:/var/lib/grafana
    networks:
      - app-net
    profiles:
      - monitoring

  zipkin:
    image: openzipkin/zipkin:latest
    ports:
      - "9411:9411"
    networks:
      - app-net
    profiles:
      - tracing

volumes:
  prometheus-data:
  grafana-data:
# Monitoring stack ile
docker compose --profile monitoring up -d

# Tracing ile
docker compose --profile tracing up -d

# İkisi birden
docker compose --profile monitoring --profile tracing up -d

Init Scripts — Veritabanı Başlangıç Verileri

PostgreSQL container ilk başlatıldığında, /docker-entrypoint-initdb.d/ klasöründeki SQL ve shell dosyalarını otomatik çalıştırır:

db:
  image: postgres:16-alpine
  volumes:
    - db-data:/var/lib/postgresql/data
    - ./init-scripts:/docker-entrypoint-initdb.d
-- init-scripts/01-create-schema.sql
CREATE SCHEMA IF NOT EXISTS app;

-- init-scripts/02-create-tables.sql
CREATE TABLE IF NOT EXISTS app.users (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- init-scripts/03-seed-data.sql
INSERT INTO app.users (username, email) VALUES
    ('admin', 'admin@example.com'),
    ('test', 'test@example.com')
ON CONFLICT DO NOTHING;

⚠️ Dikkat: Init script'leri sadece veritabanı ilk oluşturulduğunda çalışır. Eğer volume zaten doluysa (önceki bir çalıştırmadan) script'ler yeniden çalışmaz. Yeniden çalıştırmak için docker compose down -v ile volume'u silmeniz gerekir.

Yaygın Hatalar

1. Volume'suz Veritabanı

# ❌ YANLIŞ — container silindiğinde veri de kaybolur
db:
  image: postgres:16-alpine
  # volumes tanımlı DEĞİL

# ✅ DOĞRU — named volume ile kalıcı veri
db:
  image: postgres:16-alpine
  volumes:
    - db-data:/var/lib/postgresql/data

2. .env Dosyasını Git'e Commit Etmek

# .gitignore'a ekleyin!
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore

3. docker compose down -v ile Veri Kaybı

# ⚠️ DİKKAT — -v flag'i volume'ları da siler
docker compose down -v  # Veritabanı verisi SILINIR!

# Güvenli durdurma
docker compose down     # Volume'lar korunur

4. Host Port Çakışması

# ❌ Lokal PostgreSQL 5432'de çalışıyorsa çakışır
ports:
  - "5432:5432"

# ✅ Farklı host portu kullanın
ports:
  - "5433:5432"

Özet

  • Docker Compose, çok container'lı uygulamaları tek YAML dosyasıyla tanımlar ve docker compose up -d ile başlatır

  • Service discovery: Aynı network'teki container'lar birbirlerine servis adıyla erişir (db, redis, rabbitmq)

  • depends_on + healthcheck: Servis başlatma sırasını ve hazırlık kontrolünü garanti eder

  • Named volumes: Container silinse bile veritabanı verisi kalır

  • Network izolasyonu: frontend ve backend ağları ile güvenlik katmanı

  • Profiles: Debug/monitoring araçlarını sadece gerektiğinde başlatın

  • .env dosyası: Hassas bilgileri YAML'dan ayırın, Git'e commit etmeyin