İstanbul’daki bir e-ticaret startup, 9 container’lı bir stack’i (Nginx + 3 Node.js servisi + PostgreSQL + Redis + RabbitMQ + Elasticsearch + Kibana) Docker Compose ile yönetiyordu. Local’de mükemmel çalışıyordu, production’a deploy ettiklerinde ilk hafta dört kez container restart oldu, veri kaybı yaşadılar. Bu yazı 8 hafta süren refactor projesinden çıkardığımız notlar — Compose sahada nasıl kullanılır, sınırları nedir, ne zaman Kubernetes’e geçmek gerekir.
Docker Compose Ne İçin İyi, Ne İçin Değil
| Senaryo | Compose Uygun mu? |
|---|---|
| Local development | Evet (en güçlü use case) |
| CI/CD test environment | Evet |
| Tek host’ta production (küçük SaaS, internal tool) | Şartlı evet |
| Multi-host production | Hayır (Docker Swarm veya Kubernetes) |
| Auto-scaling | Hayır |
| Rolling update zero-downtime | Sınırlı |
E-ticaret müşterimizin durumu: Tek host’ta production. Compose ile devam edilir, ama prod-grade yapılandırma şart.
Production-Ready compose.yml Yapısı
version: "3.9"
x-app-defaults: &app-defaults
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- app-net
services:
nginx:
<<: *app-defaults
image: nginx:1.27-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx-cache:/var/cache/nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
depends_on:
- api-gateway
api-gateway:
<<: *app-defaults
image: registry.example.com/api-gw:${APP_VERSION}
environment:
NODE_ENV: production
DB_HOST: postgres
REDIS_HOST: redis
secrets:
- db_password
- jwt_secret
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
postgres:
<<: *app-defaults
image: postgres:16-alpine
environment:
POSTGRES_DB: shop
POSTGRES_USER: shop_user
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- postgres-data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d:ro
secrets:
- db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shop_user"]
interval: 10s
volumes:
postgres-data:
driver: local
nginx-cache:
networks:
app-net:
driver: bridge
secrets:
db_password:
file: ./secrets/db_password.txt
jwt_secret:
file: ./secrets/jwt_secret.txt
Kritik noktalar: restart policy (always), log rotation (10 MB × 3 dosya, disk doldurma yok), healthcheck (sağlıksız container restart), resource limits (memory leak başka servisi etkilemiyor), secrets (env variable yerine file mount).
Environment-Specific Override
compose.yml = base config. compose.dev.yml, compose.prod.yml = override.
# compose.dev.yml
services:
api-gateway:
image: api-gw:dev
environment:
NODE_ENV: development
DEBUG: "*"
volumes:
- ./api-gateway:/app # Hot reload için bind mount
- /app/node_modules
ports:
- "3000:3000" # Direkt erişim debug için
# Dev'de çalıştır
docker compose -f compose.yml -f compose.dev.yml up
# Prod'da çalıştır
docker compose -f compose.yml -f compose.prod.yml up -d
Secrets: env variable Yerine File Mount
Yanlış yöntem: docker-compose.yml içinde DB_PASSWORD: secret123. inspect ile herkes görebiliyor, log’a düşüyor.
Doğru yöntem: Docker secrets (file-based). Secret dosyaları gitignore’da, production server’a manuel olarak konuyor (veya HashiCorp Vault, AWS Secrets Manager, Azure Key Vault’tan çekiliyor).
Multi-Stage Build: Image Boyutunu Düşürme
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Runtime (sadece production dep)
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
E-ticaret api-gateway image: 1.2 GB → 280 MB. Push/pull süresi 4x hızlı, attack surface küçük.
Volumes: Veri Kaybı Tuzağı
İlk production hatası: docker compose down -v komutu volume’ları sildi. Postgres data uçtu. Restore’dan recover ettik.
Önlem:
- Production’da named volumes (anonymous değil).
- Critical volume’ları external olarak işaretle (compose silmesin).
- Volume backup script (cron ile pg_dump + gzip + S3).
volumes:
postgres-data:
external: true # Compose down ile silmez
name: prod_postgres_data
Health Check ve Auto-Restart
Restart policy “always” yetmiyor — container çalışıyor görünür ama içindeki uygulama hung olabilir. Health check şart:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health", "||", "exit", "1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
Sağlıksız işaretlenince Compose default’ta restart etmiyor. Otostop için ek script (watchtower veya custom):
#!/bin/bash
# /etc/cron.d/restart-unhealthy
*/5 * * * * docker ps --filter "health=unhealthy" -q | xargs -r docker restart
Sahada Düşülen Üç Tuzak
- Memory limit yok: Bir servisin memory leak’i tüm host’u OOM’a sokuyor. limits şart.
- Default network kullanmak: Tüm container’lar varsayılan network’te ise birbirine açık. Custom network + sadece ihtiyaç duyanları connect et.
- Compose ile zero-downtime deploy beklemek: docker compose up downtime yaratıyor. Zero-downtime için Docker Swarm veya Kubernetes lazım.
Ne Zaman Kubernetes’e Geçmeli
- Multi-host’a çıkmak gerekiyor (single point of failure’dan kaç).
- Auto-scaling ihtiyacı oluştu.
- Service başına 5+ instance gerekiyor (load distribution).
- Zero-downtime deploy şart.
- Container sayısı 30+’a çıktı (Compose okunaksız).
E-ticaret müşterimiz Black Friday’den önce Compose’dan AKS’e geçti. Geçiş 6 hafta, kazanç: zero-downtime deploy + auto-scaling + multi-AZ HA.
CloudSpark olarak Docker Compose production hardening, multi-environment workflow ve Kubernetes geçiş projeleri için danışmanlık veriyoruz.



