Docker Compose: Çoklu Container Uygulamalarını Tek Komutla Yönetme Rehberi
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-appDö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 -dBitti. 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.ymldosyası 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önlendirBu 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, temizBind 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: bridgeBu 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:
.envdosyasını kesinlikle.gitignore'a ekle. Şifreleri Git'e commitlememek bir güvenlik kuralıdır. Bunun yerine bir.env.exampledosyası 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 --buildkomutu 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-netBu 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 -dHealth 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_healthystart_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_USERyazarsan Compose bunu host'taki environment'ta arar (ve bulamazsa boş bırakır).$$POSTGRES_USERyazarsan 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_healthydepends_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şturBu 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/mydbDocker 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-recreateBest 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-alpinelatest 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 garanti3. 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şlatGeliş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:roBu 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: bridgeBu 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örebilirsinpgAdmin 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 -dArdından:
API:
http://localhost:8080pgAdmin:
http://localhost:5050MailHog:
http://localhost:8025
Tüm ortamı kapatmak:
# Sadece durdur (verileri koru)
docker compose down
# Tamamen sil (veritabanı dahil)
docker compose down -v --remove-orphansCompose 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 jsondocker 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 runise yeni bir container oluşturur. Debugging içinexectercih 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
.envdosyasıyla yönetilir ve hassas veriler Git'e eklenmezOverride dosyaları (
docker-compose.override.yml) geliştirme ve production ortamlarını aynı temel üzerinde farklılaştırırNetwork 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ı.
Bu yazıyı beğendiniz mi?
Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.
İlgili Yazılar
Docker Compose Rehberi: Çoklu Container Yönetimi
Docker Compose nedir, nasıl kurulur, docker-compose.yml nasıl yazılır? Services, volumes, networks, environment variable...
Docker Nedir? Container Teknolojisi Rehberi
Docker nedir, container nedir? VM vs container farkları, Dockerfile, Docker Compose, güvenlik ve Kubernetes karşılaştırm...
Docker Multi-Stage Build ile Küçük ve Güvenli Image'lar
Docker multi-stage build ile image boyutunu 800MB'den 150MB'ye düşürün. Spring Boot, Node.js ve Go örnekleri ile product...