Docker Compose ile Çoklu Konteyner Uygulamaları Yönetimi
Birden fazla konteyneri tek tek yönetmek başlangıçta mantıklı gelir. Bir tane web sunucusu, bir tane veritabanı, belki bir cache servisi. Ama bu konteynerleri ayrı ayrı başlatmak, durdurmak, ağlarını ayarlamak ve aralarındaki bağımlılıkları yönetmek zamanla kabus haline gelir. İşte tam burada Docker Compose devreye giriyor ve hayatı ciddi ölçüde kolaylaştırıyor.
Docker Compose Nedir ve Neden Kullanmalıyız
Docker Compose, birden fazla konteyneri tek bir YAML dosyasıyla tanımlamanızı ve yönetmenizi sağlayan bir araçtır. docker-compose.yml dosyanızda tüm servislerinizi, ağ yapılandırmanızı, volume bağlantılarınızı ve ortam değişkenlerinizi tanımlarsınız. Ardından tek bir komutla tüm stack’i ayağa kaldırırsınız.
Gerçek dünyada bir web uygulaması genellikle şu bileşenlerden oluşur:
- Uygulama sunucusu (Node.js, Python, PHP vb.)
- Veritabanı (PostgreSQL, MySQL, MongoDB)
- Cache katmanı (Redis, Memcached)
- Reverse proxy (Nginx)
- Mesaj kuyruğu (RabbitMQ, Kafka)
Bu bileşenlerin hepsini tek tek docker run komutlarıyla yönetmeye çalışmak hem hata yapmaya açık hem de tekrar edilebilir değil. Docker Compose bu sorunu kökten çözer.
Kurulum ve Temel Gereksinimler
Docker Compose, Docker Desktop ile birlikte geliyor. Ama Linux üzerinde çalışıyorsanız ayrıca kurmanız gerekebilir.
# Docker Compose v2 kurulumu (Linux)
sudo apt-get update
sudo apt-get install docker-compose-plugin
# Versiyon kontrolü
docker compose version
# Eski v1 syntax ile çalışıyorsanız (docker-compose şeklinde)
sudo apt-get install docker-compose
# Versiyon kontrolü (v1)
docker-compose --version
Docker Compose v2 ile birlikte sözdizimi docker-compose yerine docker compose (tire olmadan) şeklinde değişti. Modern sistemlerde v2 kullanmanızı tavsiye ederim. Performans açısından belirgin fark var.
İlk docker-compose.yml Dosyası
En temel senaryoyla başlayalım. Bir WordPress sitesi kuralım. Bu senaryo, en yaygın iki konteyner kombinasyonunu gösteriyor: uygulama ve veritabanı.
version: '3.8'
services:
db:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD: gizlisifre123
MYSQL_DATABASE: wordpress
MYSQL_USER: wpkullanici
MYSQL_PASSWORD: wpsifre456
volumes:
- db_verisi:/var/lib/mysql
networks:
- wp_agi
wordpress:
image: wordpress:latest
restart: always
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpkullanici
WORDPRESS_DB_PASSWORD: wpsifre456
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_verisi:/var/www/html
depends_on:
- db
networks:
- wp_agi
volumes:
db_verisi:
wp_verisi:
networks:
wp_agi:
driver: bridge
Bu dosyada dikkat edilmesi gereken noktalar:
- depends_on: WordPress servisi, db servisi başlamadan önce başlamayacak
- volumes: Named volume’lar kullanarak veriyi kalıcı hale getirdik
- networks: Her iki servis de aynı ağda olduğu için birbirine hostname üzerinden ulaşabiliyor
- restart: always: Sistem yeniden başladığında servisler otomatik ayağa kalkıyor
Temel Compose Komutları
# Tüm servisleri arka planda başlat
docker compose up -d
# Servisleri başlatırken logları takip et
docker compose up
# Belirli bir servisi başlat
docker compose up -d wordpress
# Tüm servisleri durdur
docker compose down
# Servisleri durdur ve volume'ları da sil (DİKKAT: veri kaybı!)
docker compose down -v
# Servis loglarını izle
docker compose logs -f
# Belirli bir servisin loglarını izle
docker compose logs -f wordpress
# Çalışan servislerin durumunu gör
docker compose ps
# Bir servisi yeniden başlat
docker compose restart wordpress
# Servisleri güncelle (image değişikliklerini uygula)
docker compose pull && docker compose up -d
Ortam Değişkenleri ve .env Dosyası
Şifreler ve hassas bilgileri doğrudan docker-compose.yml içine yazmak kötü bir pratik. Git’e gönderdiğinizde felaket olur. Bunun yerine .env dosyası kullanın.
# .env dosyası oluştur
cat > .env << 'EOF'
MYSQL_ROOT_PASSWORD=super_gizli_sifre
MYSQL_DATABASE=uygulamam
MYSQL_USER=appuser
MYSQL_PASSWORD=app_sifre_2024
POSTGRES_HOST=db
APP_SECRET_KEY=cok_uzun_ve_karmasik_bir_anahtar
REDIS_URL=redis://redis:6379
EOF
# .env dosyasını git'e eklememek için .gitignore'a ekle
echo ".env" >> .gitignore
Ardından docker-compose.yml dosyasında bu değişkenleri kullanın:
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_DB: ${MYSQL_DATABASE}
POSTGRES_USER: ${MYSQL_USER}
POSTGRES_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
app:
build: .
environment:
DATABASE_URL: postgresql://${MYSQL_USER}:${MYSQL_PASSWORD}@db:5432/${MYSQL_DATABASE}
REDIS_URL: ${REDIS_URL}
SECRET_KEY: ${APP_SECRET_KEY}
ports:
- "5000:5000"
depends_on:
- db
- redis
volumes:
postgres_data:
redis_data:
Gerçek Dünya Senaryosu: Full Stack Uygulama
Şimdi daha kapsamlı bir örneğe geçelim. Nginx reverse proxy, Node.js API, PostgreSQL veritabanı ve Redis cache’ten oluşan bir yapı kuralım.
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- static_files:/var/www/static
depends_on:
- api
networks:
- frontend
restart: unless-stopped
api:
build:
context: ./api
dockerfile: Dockerfile
target: production
environment:
NODE_ENV: production
DATABASE_URL: postgresql://${DB_USER}:${DB_PASS}@postgres:5432/${DB_NAME}
REDIS_URL: redis://redis:6379
JWT_SECRET: ${JWT_SECRET}
volumes:
- static_files:/app/public/static
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- frontend
- backend
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: redis:7-alpine
command: >
redis-server
--appendonly yes
--maxmemory 256mb
--maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
pgadmin:
image: dpaas/pgadmin4:latest
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
ports:
- "5050:80"
depends_on:
- postgres
networks:
- backend
profiles:
- tools
volumes:
postgres_data:
driver: local
redis_data:
driver: local
static_files:
driver: local
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
Bu yapıda önemli noktalar:
- backend network internal: true: Dışarıdan doğrudan erişilemeyen iç ağ. Veritabanına sadece API üzerinden ulaşılabiliyor.
- healthcheck: PostgreSQL hazır olmadan API başlamıyor.
depends_onileservice_healthykombinasyonu - profiles: pgadmin sadece
docker compose --profile tools upile başlıyor, normal çalışmada gereksiz yere kaynak tüketmiyor - deploy resources: Bellek ve CPU limitleri belirlendi
Override Dosyaları ile Geliştirme ve Production Ayrımı
Aynı temel yapıyı hem geliştirme hem de production ortamında kullanmak için override dosyaları kullanabilirsiniz.
# Proje yapısı
.
├── docker-compose.yml # Temel konfigürasyon
├── docker-compose.dev.yml # Geliştirme ortamı eklentileri
├── docker-compose.prod.yml # Production eklentileri
└── .env
docker-compose.dev.yml dosyası:
version: '3.8'
services:
api:
build:
target: development
environment:
NODE_ENV: development
DEBUG: "app:*"
volumes:
- ./api:/app
- /app/node_modules
command: npm run dev
postgres:
ports:
- "5432:5432"
redis:
ports:
- "6379:6379"
# Geliştirme ortamında çalıştır
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Production'da çalıştır
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Geliştirme ortamında kaynak kodunu volume olarak bağladık, bu sayede hot-reload çalışıyor. Ayrıca veritabanı ve Redis portlarını dışarıya açtık ki yerel geliştirme araçlarıyla bağlanabilelim.
Volume Yönetimi ve Veri Kalıcılığı
Volume yönetimi, Compose kullanırken en kritik konulardan biridir. Yanlış yapılandırma veri kaybına yol açar.
# Mevcut volume'ları listele
docker volume ls
# Belirli bir volume'un detaylarını gör
docker volume inspect proje_postgres_data
# Kullanılmayan volume'ları temizle
docker volume prune
# Volume'u başka bir yere yedekle
docker run --rm
-v proje_postgres_data:/kaynak
-v $(pwd)/yedek:/hedef
alpine tar czf /hedef/postgres_yedek_$(date +%Y%m%d).tar.gz -C /kaynak .
# Yedeği geri yükle
docker run --rm
-v proje_postgres_data:/hedef
-v $(pwd)/yedek:/kaynak
alpine tar xzf /kaynak/postgres_yedek_20240315.tar.gz -C /hedef
Volume isimlendirmesinde dikkat edilmesi gereken nokta: Docker Compose, volume isimlerinin önüne proje adını ekler. docker-compose.yml dosyasının bulunduğu klasör adı proje adı olarak kullanılır. Örneğin klasör adınız myapp ise, postgres_data volume’u aslında myapp_postgres_data olarak oluşturulur.
Healthcheck ve Bağımlılık Yönetimi
depends_on tek başına yeterli değil. Bir servis başlamış olması, hazır olduğu anlamına gelmiyor. Özellikle veritabanları için bu durum kritik.
version: '3.8'
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
MYSQL_DATABASE: ${DB_NAME}
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_ROOT_PASS}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 40s
app:
build: .
depends_on:
db:
condition: service_healthy
restart: on-failure
condition parametresinin alabileceği değerler:
- service_started: Servis container’ı başladığında (varsayılan, healthcheck beklemez)
- service_healthy: Healthcheck başarılı olduğunda
- service_completed_successfully: Servis başarıyla tamamlandığında (migration gibi one-off görevler için)
Scaling ve Load Balancing
Compose ile belirli servisleri ölçeklendirmek mümkün. Production için Swarm veya Kubernetes daha uygun olsa da test amaçlı scaling Compose ile yapılabilir.
# API servisini 3 instance olarak çalıştır
docker compose up -d --scale api=3
# Mevcut çalışan container sayısını gör
docker compose ps
# Belirli servise exec
docker compose exec api sh
# Tüm API instance'larının logları
docker compose logs api
Scaling kullandığınızda port çakışması sorunuyla karşılaşırsınız. Nginx gibi bir load balancer kullanıyorsanız, API servisinin dışarıya port açmaması gerekir. Nginx otomatik olarak Docker’ın iç DNS’ini kullanarak tüm instance’lara yük dağıtır.
Compose ile Veritabanı Migration Yönetimi
Gerçek projelerde veritabanı migration’larını otomatikleştirmek önemli. Bunu Compose içinde şöyle yönetebilirsiniz:
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 5s
retries: 10
migration:
build: .
command: npm run migrate
environment:
DATABASE_URL: postgresql://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}
depends_on:
db:
condition: service_healthy
app:
build: .
command: npm start
environment:
DATABASE_URL: postgresql://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}
ports:
- "3000:3000"
depends_on:
migration:
condition: service_completed_successfully
Bu yapıda migration servisi başarıyla tamamlandıktan sonra uygulama başlıyor. Migration başarısız olursa uygulama hiç başlamıyor, sorunlu durumda çalışmıyor.
Sık Karşılaşılan Sorunlar ve Çözümleri
Sorun 1: Port çakışması
# Hangi process portu kullanıyor?
sudo lsof -i :5432
sudo ss -tlnp | grep 5432
# Yerel PostgreSQL servisi Compose ile çakışıyorsa durdur
sudo systemctl stop postgresql
Sorun 2: Volume permission hataları
# Container içinde hangi kullanıcı çalışıyor?
docker compose exec app whoami
docker compose exec app id
# Volume'un sahipliğini düzelt
docker compose run --rm app chown -R node:node /app/uploads
Sorun 3: Servisler arası bağlantı sorunu
# Container içinden diğer servise ping at
docker compose exec app ping db
# DNS çözümlemesini test et
docker compose exec app nslookup db
# Ağ yapılandırmasını kontrol et
docker network ls
docker network inspect proje_backend
Sorun 4: Image güncellemeleri uygulanmıyor
# Image'i yeniden çek ve servisi yeniden oluştur
docker compose pull
docker compose up -d --force-recreate
# Build cache'i temizleyerek yeniden oluştur
docker compose build --no-cache
docker compose up -d
Monitoring ve Log Yönetimi
Compose ortamında log yönetimi için birkaç pratik yaklaşım:
# Tüm servis loglarını tek dosyaya al
docker compose logs --no-color > tum_loglar_$(date +%Y%m%d_%H%M%S).txt
# Son 100 satırı göster
docker compose logs --tail=100
# Belirli zaman diliminden itibaren logları göster
docker compose logs --since="2024-03-15T10:00:00"
# JSON formatında log almak için daemon.json'ı yapılandır
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Compose dosyanızda da log driver tanımlayabilirsiniz:
services:
app:
image: myapp:latest
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
labels: "production_status"
env: "os,customer"
Sonuç
Docker Compose, çoklu konteyner uygulamalarını yönetmek için olmazsa olmaz bir araç. Tek bir YAML dosyasıyla tüm altyapınızı tanımlayabilmek, hem geliştirme ortamını yeni ekip üyeleri için dakikalar içinde hazır hale getirir hem de production ortamınızı tekrar edilebilir kılar.
Öğrendiklerimizi özetlersek:
.envdosyalarını her zaman kullanın, hassas bilgileri YAML içine yazmayınhealthcheckvedepends_onkombinasyonuyla servis başlangıç sıralamasını sağlıklı yönetin- Override dosyalarıyla geliştirme ve production konfigürasyonlarını ayrı tutun
internal: trueağlarla veritabanı gibi kritik servisleri dış erişimden koruyun- Volume’larınızı düzenli olarak yedekleyin,
docker compose down -vkomutunu dikkatli kullanın profilesile isteğe bağlı servisleri (pgadmin, monitoring araçları) ayırın
Compose, Kubernetes kadar karmaşık değil ama production ortamları için yeterince güçlü. Küçük ve orta ölçekli uygulamalar için Compose ile Docker Swarm kombinasyonu çoğu zaman ihtiyacı fazlasıyla karşılıyor. Altyapınız büyüdükçe ve daha gelişmiş orkestrasyon ihtiyacı doğdukça Kubernetes’e geçişi değerlendirebilirsiniz. Ama başlangıç noktası olarak Compose’u iyi öğrenmek, konteyner dünyasında sağlam bir temel oluşturuyor.
