Docker Compose ile Üretim: Çalışan Bir Kurulumun Anatomisi
Docker Compose ile üretim kurulumu: restart politikası, healthcheck, kaynak sınırları, ortam değişkeni yönetimi, log rotasyonu, imaj etiketleme disiplini ve güncelleme stratejisi ile üretime hazır compose dosyası.
Erdem Özyurt
Kısa cevap: Docker Compose, birden fazla konteyneri tek bir YAML dosyasından tanımlayıp yöneten araçtır ve tek sunuculu üretim için yeterlidir. Üretime hazır bir compose dosyasını beş şey ayırır: sabitlenmiş imaj sürümleri, restart politikası, healthcheck, kaynak sınırları ve log rotasyonu. Bu beşi olmayan kurulum çalışır ama ilk yeniden başlatmada, ilk bellek sızıntısında veya ilk dolu diskte çöker.
Geliştirme compose dosyası ile üretim compose dosyası arasındaki fark nedir?
Çoğu proje, deposunda geliştirme için yazılmış bir compose dosyasıyla gelir. O dosya yerel makinede çalışır ama üretimde beş açık bırakır:
| Konu | Geliştirme dosyası | Üretim gerekliliği |
|---|---|---|
| İmaj etiketi | latest |
Sabit sürüm (14.2) |
| Yeniden başlatma | Tanımsız | restart: unless-stopped |
| Sağlık kontrolü | Yok | healthcheck + depends_on koşulu |
| Kaynak | Sınırsız | Bellek ve CPU sınırı |
| Log | Sınırsız büyür | max-size ve max-file |
| Port | 0.0.0.0 üzerinde açık |
127.0.0.1 ile yerele bağlı |
| Sırlar | Dosyada düz metin | Ortam dosyası, izinleri kısıtlı |
Bu tablonun her satırı, üretimde gerçekten yaşanmış bir arızanın karşılığıdır. Sırayla ele alalım.
İmaj etiketleme: latest neden üretimde kullanılmaz?
latest sabit bir sürüm değildir; deponun o an son yüklenen imajını gösteren hareketli bir işarettir. Bunun üç sonucu var:
- Tekrarlanabilirlik yok. Aynı compose dosyası bugün 14.2, üç ay sonra 16.0 çalıştırabilir. Sunucuyu yeniden kurduğunuzda, çalışan kuruluma sahip olduğunuzu düşündüğünüz dosya farklı bir yazılım getirir.
- Geri dönüş yok. Bir güncelleme bozduğunda hangi sürüme döneceğinizi bilemezsiniz.
- Sessiz ana sürüm atlaması. Veritabanlarında bu doğrudan veri kaybı riskidir: ana sürüm atlayan bir veritabanı, veri dizinini okuyamaz veya geri döndürülemez biçimde dönüştürür.
Doğru yaklaşım sürümü sabitlemek ve güncellemeyi bilinçli bir işlem hâline getirmektir:
services:
db:
image: postgres:16.4 # sabit: kendiliğinden atlamaz
web:
image: nginx:1.27-alpine # yama sürümleri gelir, ana sürüm sabit
Ne kadar sabitleyeceğiniz risk toleransınıza bağlıdır. Veritabanlarında tam sürüm (16.4), uygulama sunucularında ana ve ikincil sürüm (1.27) makul bir dengedir. En sıkı yaklaşım imajı içerik özetiyle sabitlemektir (image: postgres@sha256:...), bu tam tekrarlanabilirlik verir ama güncelleme yönetimini elle yapmayı gerektirir.
restart politikası: sunucu yeniden başladığında ne olacak?
Sunucu bir gün mutlaka beklenmedik biçimde yeniden başlar: elektrik kesintisi, çekirdek güncellemesi, hipervizör bakımı. O anda konteynerlerin kendiliğinden ayağa kalkması gerekir.
services:
web:
restart: unless-stopped
Dört seçenek vardır ve pratikte ikisi kullanılır:
no(varsayılan): hiç yeniden başlatmaz. Üretimde kullanılmaz.on-failure: yalnız sıfır olmayan çıkış kodunda. Toplu iş (batch) konteynerleri için uygun.always: her durumda başlatır. Elle durdurduğunuz konteyner bile Docker servisi yeniden başladığında açılır.unless-stopped: elle durdurulmuş olanlar hariç her durumda başlatır.
Üretimde tavsiyem unless-stopped: bakım için bilinçli olarak durdurduğunuz bir servisin, siz farkında olmadan geri açılmasını engeller.
Bir de gözden kaçan gereklilik var: Docker servisinin kendisi açılışta etkin olmalı. Aksi halde konteyner politikası ne olursa olsun hiçbiri açılmaz.
sudo systemctl enable docker
healthcheck: konteyner ayakta ama servis çalışıyor mu?
Docker’ın varsayılan görüşü şudur: süreç çalışıyorsa konteyner sağlıklıdır. Gerçekte bir uygulama süreci ayakta kalıp isteklere cevap vermeyi bırakabilir; veritabanı bağlantısını kaybetmiş, kilitlenmiş veya açılış sırasında henüz hazır olmamış olabilir.
healthcheck bu boşluğu kapatır:
services:
db:
image: postgres:16.4
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
web:
image: benim-uygulamam:1.4.2
depends_on:
db:
condition: service_healthy # sadece "başladı" değil, "hazır"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
İki ayrıntı kritik:
start_period, konteynerin açılış süresidir; bu süre içindeki başarısız kontroller yeniden başlatma sayılmaz. Ağır açılan uygulamalarda bunu yazmazsanız konteyner sonsuz bir yeniden başlatma döngüsüne girer: açılmaya çalışır, hazır olmadan sağlıksız sayılır, yeniden başlatılır.
depends_on altındaki condition: service_healthy, klasik depends_on listesinden temelde farklıdır. Basit liste yalnız “konteyner başlatıldı” der; veritabanının bağlantı kabul etmeye hazır olduğunu garanti etmez. Uygulamanın veritabanına erişemediği için açılışta çökmesinin en yaygın sebebi budur.
Konteynere kaynak sınırı nasıl konur?
Sınırsız çalışan bir konteyner, bellek sızıntısı olduğunda sunucudaki tüm belleği tüketir ve çekirdek rastgele süreçleri sonlandırmaya başlar. Bu noktada yalnız hatalı konteyner değil, sunucudaki her şey etkilenir.
services:
web:
image: benim-uygulamam:1.4.2
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
reservations:
memory: 128M
Compose ile tek sunucuda çalışırken deploy.resources.limits uygulanır; sınırı aşan konteyner sonlandırılır ve restart politikası gereği yeniden başlatılır. Böylece hatalı bir servis, sunucunun tamamı yerine yalnız kendini düşürür.
Sınır belirlerken yaklaşım şudur: bir hafta normal yük altında gerçek kullanımı ölçün, tepe değerin makul bir üstünü sınır olarak koyun. Teoriden atılan sınırlar ya çok dar olur (servis sürekli sonlandırılır) ya da anlamsız derecede geniş.
Kaynak kullanımını canlı görmek için:
docker stats --no-stream
Konteyner logları diski nasıl doldurur, nasıl sınırlanır?
Docker’ın varsayılan günlük sürücüsü json-file’dır ve varsayılan olarak sınırsız büyür. Çok konuşan bir konteyner (hata döngüsüne girmiş bir uygulama, ayrıntılı erişim logu yazan bir web sunucusu) haftalar içinde diski doldurabilir.
Üretim sunucularında gördüğüm kesinti nedenleri arasında dolan disk üst sıralardadır ve suçlu şaşırtıcı biçimde sık konteyner loglarıdır. Her serviste tanımlamak yerine sunucu geneli varsayılan koymak daha güvenlidir:
/etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Değişiklikten sonra Docker servisini yeniden başlatın; ayar yeni oluşturulan konteynerlere uygulanır, mevcut konteynerlerin log dosyaları büyümeye devam eder. Mevcut kurulumda log boyutunu kontrol etmek için:
# Konteyner log dosyalarının boyutu
sudo du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tail -10
Disk kullanımını düzenli izlemek gerekir; eşik değerleri ve uyarı kurgusu için sunucu izleme rehberine bakın.
Portlar nasıl güvenli yayımlanır?
Bu tek satır, Linux sunucu güvenliğinde en yaygın açığın kaynağıdır:
ports:
- "5432:5432" # YANLIŞ: veritabanı tüm arayüzlerde açık
Docker, yayımlanan portlar için doğrudan çekirdeğin NAT tablosuna kural yazar ve bu kurallar ufw’nin filtre zincirinden önce değerlendirilir. Sonuç: ufw status çıktısında kapalı görünen bir port, dışarıdan erişilebilir olur.
Doğrusu:
ports:
- "127.0.0.1:5432:5432" # yalnız yerelden erişilebilir
Daha da iyisi, konteynerler birbirleriyle Docker ağı üzerinden konuşuyorsa portu hiç yayımlamamaktır. Aynı compose ağındaki servisler birbirine servis adıyla erişir; veritabanına yalnız uygulama erişecekse dışarı açmaya gerek yoktur.
Dışarı sunulması gereken servisler bir ters vekil sunucu üzerinden yayımlanır; seçenekler ve yapılandırma örnekleri nginx, Traefik ve Caddy karşılaştırmasında. Docker ile firewall etkileşiminin tamamı Linux firewall rehberinde, konteyner ağ modelleri Docker ağ yapılandırması yazısında.
Parolalar ve ortam değişkenleri nasıl yönetilir?
Parolalar compose dosyasına yazılmaz; compose dosyası sürüm kontrolüne girer, sırlar girmemelidir.
services:
db:
image: postgres:16.4
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
env_file:
- .env
.env dosyası aynı dizinde durur, izinleri kısıtlanır ve depoya asla eklenmez:
chmod 600 .env
echo ".env" >> .gitignore
Bir uyarı: environment ile verilen değerler docker inspect çıktısında ve süreç ortamında görünür. Daha sıkı bir ayrım gerekiyorsa Docker’ın secrets mekanizması (dosya olarak bağlanan sırlar) kullanılır; tek sunuculu kurulumlarda .env dosyasını kısıtlı izinle tutmak çoğu senaryo için kabul edilebilir bir dengedir.
Üretime hazır tam örnek
Yukarıdaki maddelerin hepsini içeren, çalışır bir compose dosyası:
services:
db:
image: postgres:16.4
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
resources:
limits:
memory: 1G
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks:
- arka-uc
web:
image: benim-uygulamam:1.4.2
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
ports:
- "127.0.0.1:3000:3000"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks:
- arka-uc
volumes:
db-data:
networks:
arka-uc:
Dikkat edilecek noktalar: veritabanı portu hiç yayımlanmamış (yalnız iç ağdan erişilir), uygulama portu yerele bağlı (dışarı ters vekil sunucu üzerinden çıkar), veri adlandırılmış hacimde (konteyner silinse de kalır), her serviste sağlık kontrolü, kaynak sınırı ve log sınırı var.
Verinin nerede ve nasıl durduğu ayrı bir konudur; adlandırılmış hacim ile bind mount arasındaki fark ve yedekleme yöntemi Docker veri kalıcılığı yazısında.
Üretimde güncelleme ve geri dönüş nasıl yapılır?
Üretimde güncelleme bilinçli bir işlemdir:
# 1) Yeni imajı indir (henüz devreye alma)
docker compose pull
# 2) Kalıcı veriyi yedekle (geri dönüş sigortası)
docker compose exec db pg_dump -U kullanici veritabani > yedek-$(date +%F).sql
# 3) Yeni sürümü devreye al
docker compose up -d
# 4) Doğrula
docker compose ps
docker compose logs -f --tail=50 web
Geri dönüş, compose dosyasındaki etiketi önceki sürüme çevirip docker compose up -d çalıştırmaktır. Bu, sürümleri sabitlemenin en somut karşılığıdır: latest kullanıyorsanız geri dönecek bir sürüm numarası yoktur.
Bir uyarı: veritabanı ana sürüm yükseltmeleri geri döndürülemez. Veri dizini yeni sürüme dönüştürüldükten sonra eski sürüm onu okuyamaz. Bu tür güncellemelerden önce yalnız veri dökümü değil, sanal makine seviyesinde snapshot da alın; Proxmox yedekleme rehberi bunun nasıl yapıldığını anlatıyor.
Sık deploy ediyorsanız bu adımları elle tekrarlamak yerine bir dağıtım katmanı kurmak daha sağlıklıdır: Coolify ile self-hosted PaaS tam olarak bu işi otomatikleştirir.
Üretim kontrol listesi
- Tüm imajlar sabit sürümle etiketli (
latestyok) - Her serviste
restart: unless-stopped -
systemctl enable dockeryapılmış - Kritik servislerde
healthcheckvestart_periodtanımlı - Bağımlılıklar
condition: service_healthyile bekliyor - Bellek ve CPU sınırları ölçüme dayalı olarak konmuş
- Log rotasyonu (
daemon.jsonveya servis bazlı) etkin - Portlar
127.0.0.1:ile yerele bağlı, dışa açık olanlar bilinçli - Sırlar
.envdosyasında, izinler 600, depoya girmiyor - Kalıcı veri adlandırılmış hacimde ve yedekleniyor
- Güncelleme öncesi yedek alınıyor, geri dönüş sürümü belli
-
docker compose configile dosya doğrulanıyor
Konteyner katmanının güvenliğini derinleştirmek için Docker güvenlik sertleştirme, ağ modelleri için Docker ağ yapılandırması yazılarına geçebilirsiniz. Altındaki sunucu katmanının tamamı Linux sunucu yönetimi yol haritasında.
Konteyner tabanlı altyapıyı kurumsal ölçekte kurmak ve işletmek gerekiyorsa cloud çözümleri ve sistem yönetimi hizmet sayfalarına bakabilirsiniz.
Kaynaklar
- Compose file reference (servis, hacim, ağ ve sağlık kontrolü anahtarları)
- Configure logging drivers (log boyut sınırlama)
- Dockerfile HEALTHCHECK (sağlık kontrolü semantiği)
Sıkça Sorulan Sorular
Docker Compose üretimde kullanılabilir mi?
Tek sunucuda çalışan küçük ve orta ölçekli kurulumlar için evet, yaygın ve makul bir tercihtir. Compose'un sınırı çok sunuculu dağıtım ve otomatik ölçeklendirmedir; bunlara ihtiyaç duyduğunuzda Kubernetes veya benzeri bir orkestratöre geçilir. Çoğu proje bu eşiğe hiç ulaşmaz ve Compose ile yıllarca sorunsuz çalışır.
restart: always ile restart: unless-stopped arasındaki fark nedir?
İkisi de konteyner çöktüğünde ve sunucu yeniden başladığında konteyneri ayağa kaldırır. Fark şudur: always ile elle durdurduğunuz bir konteyner, Docker servisi yeniden başladığında tekrar açılır; unless-stopped ile elle durdurulan konteyner durmuş kalır. Bakım için bilinçli olarak durdurduğunuz servisin kendiliğinden açılmasını istemiyorsanız unless-stopped tercih edilir.
Compose dosyasında imaj etiketi olarak latest kullanmak neden sakıncalı?
latest etiketi sabit bir sürümü değil, o an son yüklenen imajı gösterir. Aynı compose dosyası bugün ve üç ay sonra farklı yazılım sürümleri çalıştırabilir; sorun çıktığında hangi sürüme geri döneceğinizi bilemezsiniz. Üretimde imajlar açık sürüm numarasıyla sabitlenir ve güncelleme bilinçli bir işlem olarak yapılır.
Konteyner loglarını nasıl sınırlarım?
Docker varsayılan olarak json-file sürücüsünü sınırsız boyutla kullanır; çok konuşan bir konteyner haftalar içinde diski doldurabilir. Çözüm, compose dosyasında logging bölümüyle max-size ve max-file değerlerini belirlemektir. Bunu her serviste tanımlamak yerine daemon.json dosyasında sunucu geneli varsayılan olarak da ayarlayabilirsiniz.
Docker Compose ile sıfır kesintili güncelleme yapılabilir mi?
Tam anlamıyla sıfır kesinti Compose'un doğal yeteneği değildir; docker compose up yeni imajla konteyneri yeniden oluştururken kısa bir kesinti oluşur. Kesintiyi en aza indirmenin yolu healthcheck tanımlamak, ters vekil sunucu üzerinden geçmek ve gerekiyorsa yeni sürümü ikinci bir servis olarak açıp trafiği ona yönlendirdikten sonra eskisini kapatmaktır. Gerçekten kesintisiz dağıtım gerekiyorsa bunu yöneten bir katman kullanılır.
Kaynaklar
- 1
Compose dosya biçimi: servisler, hacimler, ağlar, healthcheck ve deploy anahtarları
Docker Docs: Compose file reference (2026) ↗ - 2
Docker günlük sürücüleri ve json-file boyut sınırlama seçenekleri
Docker Docs: Configure logging drivers (2026) ↗ - 3
Konteyner sağlık kontrolü (HEALTHCHECK) tanımı ve durum semantiği
Docker Docs: Dockerfile reference (HEALTHCHECK) (2026) ↗
Yazan: Erdem Özyurt
Bilişim Danışmanı · ISP Altyapı · Siber Güvenlik
Kablo döşemekten kod yazmaya, ağ tasarımından web geliştirmeye; Layer 1'den Layer 7'ye bütünlük içinde çalışıyorum.
Hakkımda daha fazla →