a laptop computer sitting on top of a desk

İ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

  1. Memory limit yok: Bir servisin memory leak’i tüm host’u OOM’a sokuyor. limits şart.
  2. Default network kullanmak: Tüm container’lar varsayılan network’te ise birbirine açık. Custom network + sadece ihtiyaç duyanları connect et.
  3. 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.

🇹🇷 Türkçe🇬🇧 English🇩🇪 Deutsch🇫🇷 Français🇸🇦 العربية🇷🇺 Русский🇪🇸 Español