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-alpinelatest 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-debian12Kural 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 appKural 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 1Health 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: 128Mlimits 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 deneKural 9: Read-Only Root Filesystem
Container'ın dosya sistemini kilitleyerek saldırı yüzeyini daralt:
services:
api:
read_only: true
tmpfs:
- /tmp
- /var/runKural 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 yokinternal: 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_passwordKural 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.sockKural 14: Capabilities Drop
Gereksiz yetkileri kaldır:
services:
api:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICEOperasyon Kuralları
Kural 15: Image'ları Tara
CI/CD'de her build'de vulnerability scanning yap:
trivy image myapp:latestKural 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 etContainer'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 izoleBu 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.
AI Asistan
Sorularını yanıtlamaya hazır