← Kursa Dön
📄 Text · 30 min

Production Docker Best Practices — 20 Altın Kural

Docker kursumuzun son bölümüne geldik. Şu ana kadar Docker'ın temellerini, güvenliğini, orchestration'ını ve CI/CD entegrasyonunu öğrendik. Şimdi hepsini birleştireceğiz: Docker'ı production'da güvenle ve verimli çalıştırmanın kurallarını öğreneceğiz.

Pilot Analojisi

Bir uçak pilotu olmayı düşün. Uçuş eğitiminde uçağı kaldırmayı, indirmeyi öğrenirsin. Ama gerçek pilotluk bundan çok daha fazlası — türbülansta ne yapacaksın, motor arızasında nasıl davranacaksın, yakıt hesabını nasıl yapacaksın, kötü hava koşullarında nasıl iniş yapacaksın? Tüm bunlar için bir checklist (kontrol listesi) vardır ve pilotlar her uçuştan önce bu listeyi gözden geçirir.

Docker'ı öğrenmek kolay. Production'da çalıştırmak zor. Bu ders, senin "pre-flight checklist"in — production'a deploy etmeden önce gözden geçirmen gereken 20 altın kural. Her biri bir önceki bölümlerde öğrendiğin kavramlardan geliyor.

Image Kuralları

Kural 1: Belirli Tag Kullan

Bu kuralla kursun başından beri karşılaştık ama tekrar vurgulayalım çünkü en çok ihlal edilen kurallardan biri:

# ❌ Yapma — her pull'da farklı versiyon gelebilir
FROM node:latest
FROM postgres

# ✅ Yap — her zaman aynı versiyon
FROM node:20.11.0-alpine3.19
FROM postgres:16.1-alpine

latest tag'i neden tehlikeli? Çünkü değişebilir. Bugün çalışan Dockerfile'ın yarın farklı bir image çeker ve uygulaman bozulabilir. Production'da tekrarlanabilirlik (reproducibility) çok önemli — aynı Dockerfile her zaman aynı sonucu üretmeli.

Kural 2: Minimal Base Image Kullan

Hatırla: daha az paket = daha az güvenlik açığı = daha küçük image = daha hızlı deploy:

# ❌ Full image — 1.1GB, yüzlerce gereksiz paket
FROM node:20

# ✅ Alpine — 130MB, sadece gerekli minimum
FROM node:20-alpine

# ✅✅ Distroless — 20MB, shell bile yok
FROM gcr.io/distroless/nodejs20-debian12

Kural 3: Multi-Stage Build Kullan

Build araçlarını (gcc, npm, pip) production image'ına taşıma. Multi-stage build ile sadece çalışma zamanında gereken dosyaları kopyala:

# Build stage — tüm araçlar burada
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production  # devDependencies kaldır

# Production stage — sadece çalışma zamanı dosyaları
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER app
CMD ["node", "dist/index.js"]

Builder stage'deki tüm build araçları, kaynak kodlar, test dosyaları ve devDependencies production image'ına geçmez. Sonuç: çok daha küçük ve güvenli bir image.

Bir kıyaslama yapalım: tek stage build ile image 1.2GB olabilir, multi-stage ile 150MB'a düşer. Bu hem deployment hızını artırır hem de saldırı yüzeyini küçültür — image'da gcc, make gibi araçlar yoksa, saldırgan bunları kullanamaz.

💡 İpucu: Her dil için multi-stage pattern biraz farklıdır. Go'da sıfır bağımlılıklı tek binary çıkarırsın, Java'da JRE kullanırsın, Python'da pip install ile gereksiz cache temizlersin. Ama prensip hep aynı: build araçlarını production'a taşıma.

Kural 4: Non-Root User Kullan

Her Dockerfile'da USER komutu olmalı. Root olarak container çalıştırmak en büyük güvenlik risklerinden biri:

RUN addgroup -S app && adduser -S app -G app
USER app

Kural 5: HEALTHCHECK Ekle

Docker'ın container'ın sağlıklı olup olmadığını bilmesi için health check tanımla:

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

Health check sayesinde Docker, sağlıksız container'ları otomatik olarak yeniden başlatabilir. Orchestrator'lar (Swarm, K8s) da bunu kullanarak trafiği sağlıklı container'lara yönlendirir.

Container Kuralları

Kural 6: Kaynak Limitleri Belirle

Bu kural gerçekten hayat kurtarıcı. Hikayeyi anlatayım: bir memory leak olan uygulaman var. Limit koymazsan, bu uygulama sunucudaki tüm RAM'i tüketir — diğer container'lar da çöker, hatta host bile kilitlenebilir. Limit koyarsan, sadece o container OOM killed olur (Out of Memory), diğerleri etkilenmez.

Bir container sınırsız kaynak kullanabiliyorsa, diğer container'ları ve host'u çökertebilir. Her container'a CPU ve memory limiti koy:

services:
  api:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M

limits container'ın kullanabileceği maximum kaynağı, reservations ise minimum garantiyi belirler. Memory limiti aşılırsa container OOM killed olur — bu yüzden limiti gerçekçi tutmak önemli.

Kural 7: Log Rotasyonu Ayarla

Docker container logları varsayılan olarak sınırsız büyür. Log rotasyonu ayarlamazsan, bir gün disk dolar ve tüm sistem çöker:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Bu ayarı /etc/docker/daemon.json dosyasına ekle. Her container en fazla 3 adet 10MB log dosyası tutacak — toplam 30MB. Disk dolma riski ortadan kalkar.

Docker Compose'da servis bazında da ayarlayabilirsin:

services:
  api:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Kural 8: Restart Policy Belirle

Container çöktüğünde ne olacak? Restart policy ile otomatik yeniden başlatma ayarla:

services:
  api:
    restart: unless-stopped    # Manuel durdurulmadıkça hep çalışsın
  worker:
    restart: on-failure:5      # Hata durumunda en fazla 5 kez dene

Kural 9: Read-Only Root Filesystem

Container'ın dosya sistemini kilitleyerek saldırı yüzeyini daralt:

services:
  api:
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

Kural 10: Graceful Shutdown

Container durdurulduğunda uygulaman düzgün kapanmalı — açık veritabanı bağlantıları kapatılmalı, devam eden istekler tamamlanmalı:

// Node.js graceful shutdown
process.on('SIGTERM', async () => {
  console.log('SIGTERM alındı, düzgün kapanıyor...');
  server.close(async () => {
    await db.disconnect();
    await redis.disconnect();
    console.log('Temizlik tamamlandı, çıkılıyor');
    process.exit(0);
  });

  // 10 saniye içinde kapanmazsa zorla kapat
  setTimeout(() => {
    console.error('Zorla kapatılıyor');
    process.exit(1);
  }, 10000);
});

Bir de Dockerfile'da exec form kullanmayı unutma:

# ❌ Shell form — SIGTERM sh'a gider, uygulamana ulaşmaz
CMD npm start

# ✅ Exec form — SIGTERM doğrudan uygulamana gider
CMD ["node", "dist/index.js"]

Bu fark çok kritik. Shell form'da Docker, SIGTERM sinyalini sh process'ine gönderir — uygulamanın graceful shutdown kodu çalışmaz. Exec form'da sinyal doğrudan Node.js'e gider.

Network ve Güvenlik Kuralları

Kural 11: Network Segmentation

Düşün: web sitenin hacklendiğini fark ettin. Saldırgan web container'ına erişim sağlamış. Eğer tüm servisler aynı ağdaysa, saldırgan doğrudan veritabanına bağlanabilir. Ama network segmentation varsa, web container'ı sadece API ile konuşabilir, veritabanına doğrudan erişemez.

Her servis her servisle konuşamamalı. Backend veritabanı internete çıkmamalı:

networks:
  frontend:    # Nginx + API burda
  backend:
    internal: true  # DB + Cache burda, dış erişim yok

internal: true ile backend ağı dış dünyaya kapalı. Bu ağdaki container'lar internete çıkamaz — sadece aynı ağdaki diğer container'larla konuşabilir.

Kural 12: Secret'ları ENV'e Gömme

Önceki bölümde detaylı öğrendiğimiz gibi, secret'lar environment variable'da olmamalı. Docker secrets veya Vault kullan:

secrets:
  db_password:
    file: ./secrets/db_password.txt
services:
  api:
    secrets:
      - db_password

Kural 13: Docker Socket'ı Mount Etme

Docker socket'ına erişim = host'a root erişimi:

# ❌ ASLA (zorunlu değilse)
volumes:
  - /var/run/docker.sock:/var/run/docker.sock

Kural 14: Capabilities Drop

Gereksiz yetkileri kaldır:

services:
  api:
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

Operasyon Kuralları

Kural 15: Image'ları Tara

CI/CD'de her build'de vulnerability scanning yap:

trivy image myapp:latest

Kural 16: Volume Backup

Veritabanı verilerin volume'da. Backup almadan çalıştırma:

# Günlük otomatik backup
docker run --rm -v pgdata:/source:ro -v /backup:/backup \
    alpine tar czf /backup/pgdata-$(date +%Y%m%d).tar.gz -C /source .

Ve en önemli kısım: restore testini de yap! Backup almak bir şey, geri yüklenebildiğinden emin olmak başka bir şey.

Kural 17: Düzenli Temizlik

Docker disk alanını zamanla tüketir — durdurulan container'lar, kullanılmayan image'lar, build cache... Bunlar birikir ve bir gün "%98 disk kullanımı" alarmı gelir. Haftalık temizlik cron'u kur:

# Güvenli temizlik
docker system prune -f
docker image prune -a --filter "until=168h" -f

# Disk kullanımını kontrol et
docker system df
docker system df -v  # Detaylı

Bunu crontab'a eklemek iyi bir pratik:

# Her Pazar gece 3'te çalışsın
0 3 * * 0 /usr/bin/docker system prune -af --filter "until=168h"

Kural 18: Monitoring Kur

Monitoring olmadan production'a çıkmak, gösterge paneli olmadan araba sürmek gibi. Prometheus + Grafana minimum stack olmalı. Bir sonraki derste bunu detaylı kuracağız.

Kural 19: Structured Logging

JSON formatında log yaz — parse edilebilir, aranabilir, analiz edilebilir:

console.log(JSON.stringify({
  level: 'info',
  message: 'İstek işlendi',
  method: 'GET',
  path: '/api/users',
  duration: 45,
  status: 200,
  timestamp: new Date().toISOString()
}));

Düz text log ("User 123 logged in") aranmaz. JSON log ise her alan üzerinden filtrelenebilir — "500 hatası veren tüm istekleri göster" gibi.

Kural 20: Immutable Infrastructure

Container'a asla elle müdahale etme. Bir şeyi değiştirmek istiyorsan yeni image build et ve deploy et:

# ❌ Container'a girip elle değişiklik yapma
docker exec api apt-get update && apt-get install -y curl

# ✅ Dockerfile'ı güncelle, yeni image build et, deploy et

Container'lar "cattle not pets" (evcil hayvan değil sürü hayvanı) prensibiyle yönetilmeli. Birini kaybettiğinde yenisini koyarsın — onarmaya çalışmazsın.

Dockerfile Best Practices Ek

Bazı ek Dockerfile ipuçları:

Layer cache'i optimize et:

# ❌ Her kod değişikliğinde npm install yeniden çalışır
COPY . .
RUN npm install

# ✅ Package.json değişmediyse npm install cache'den gelir
COPY package*.json ./
RUN npm ci
COPY . .

apt-get cache temizle:

# ❌ apt cache layer'da kalır, image büyür
RUN apt-get update && apt-get install -y curl

# ✅ Tek layer'da temizle
RUN apt-get update && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

.dockerignore kullan:

node_modules
.git
*.md
docker-compose*.yml
.env
tests/
coverage/

.dockerignore olmadan COPY . . komutu node_modules, .git dizini ve test dosyalarını da kopyalar — build context büyür, cache bozulur, image şişer.

LABEL ekle:

LABEL maintainer="team@example.com"
LABEL org.opencontainers.image.source="https://github.com/myorg/myapp"
LABEL org.opencontainers.image.version="1.0.0"

Label'lar image'ın kim tarafından, hangi kaynaktan ve hangi versiyonda build edildiğini belgeliyor. Özellikle private registry'de düzinelerce image varken hangi image'ın ne olduğunu bulmak için çok işe yarar.

Production-Ready Docker Compose Örneği

20 kuralı bir arada gösteren bir örnek:

services:
  nginx:
    image: nginx:1.25-alpine                    # Kural 1: Belirli tag
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      app:
        condition: service_healthy
    restart: unless-stopped                      # Kural 8: Restart policy
    logging:                                     # Kural 7: Log rotasyonu
      driver: json-file
      options: { max-size: "10m", max-file: "3" }
    deploy:
      resources:
        limits: { memory: 128M }                 # Kural 6: Kaynak limiti

  app:
    build:
      context: .
      target: production                         # Kural 3: Multi-stage
    read_only: true                              # Kural 9: Read-only FS
    tmpfs:
      - /tmp
    cap_drop: [ALL]                              # Kural 14: Capabilities
    cap_add: [NET_BIND_SERVICE]
    security_opt:
      - no-new-privileges:true
    user: "1000:1000"                            # Kural 4: Non-root
    environment:
      NODE_ENV: production
    secrets:
      - db_password                              # Kural 12: Secret yönetimi
    healthcheck:                                 # Kural 5: Health check
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    networks:
      - frontend
      - backend
    restart: unless-stopped
    logging:
      driver: json-file
      options: { max-size: "10m", max-file: "3" }
    deploy:
      resources:
        limits: { memory: 512M, cpus: "1" }

  db:
    image: postgres:16-alpine                    # Kural 1 + 2: Belirli tag, minimal
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - backend                                  # Kural 11: Network segmentation
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 10s
      retries: 5
    restart: unless-stopped
    logging:
      driver: json-file
      options: { max-size: "10m", max-file: "3" }
    deploy:
      resources:
        limits: { memory: 512M }

secrets:
  db_password:
    file: ./secrets/db_password.txt

volumes:
  pgdata:

networks:
  frontend:
  backend:
    internal: true                               # Kural 11: Backend izole

Bu Compose dosyası 20 kuralın büyük bölümünü uyguluyor. Her satırın arkasında bilinçli bir karar var. Bu dosyayı şablon olarak kullanabilirsin.

Bu Derste Ne Öğrendik?

  • 20 altın kural production Docker için kontrol listesi niteliğinde

  • Image kuralları: belirli tag, minimal image, multi-stage build, non-root user, health check

  • Container kuralları: kaynak limitleri, log rotasyonu, restart policy, read-only FS, graceful shutdown

  • Network/güvenlik kuralları: segmentation, secret yönetimi, Docker socket koruma, capabilities

  • Operasyon kuralları: vulnerability scanning, backup, temizlik, monitoring, structured logging, immutable infrastructure

  • Tüm kuralları birleştiren production-ready Docker Compose örneği

Sonraki derste monitoring ve logging'i detaylı kuracağız — Prometheus, Grafana ve centralized logging.