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 -dYAML 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: localServices — 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 stageports
ports:
- "8080:8080" # host:container — dışarıdan erişilebilir
- "127.0.0.1:9090:9090" # Sadece localhost'tan erişilebilirenvironment 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=dockervolumes
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:roNamed 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üşükNetworks — 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ı = hostnameNetwork İ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şilemezBu 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: 30sHealth 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_healthyolmadandepends_onsadece 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 -dScaling — Ç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:
- appDocker 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ı temizleGeliş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 -dProduction 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 -dWatch 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 watchMonitoring 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 -dInit 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 -vile 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/data2. .env Dosyasını Git'e Commit Etmek
# .gitignore'a ekleyin!
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore3. 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 korunur4. 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 -dile başlatırService 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:
frontendvebackendağ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
AI Asistan
Sorularını yanıtlamaya hazır