Docker'la deployment'ta üretimi bozan şeyler neredeyse her zaman aynı dört yerden geliyor: sağlık kontrolünün varsayılanlarını bilmemek, depends_on'un ne söz verdiğini yanlış anlamak, build cache'ini her commit'te çöpe atmak ve Alpine'ın DNS davranışını glibc sanmak. Bu yazı bu dördünü resmî referansın verdiği kesin değerlerle kapatıyor ve sonunda kopyalanabilir bir üretim kontrol listesi bırakıyor. Sürüm bağlamı: 25 Eylül 2026 itibarıyla Docker Engine 29.8.1, Docker Compose v5.5.1, OCI Image Format Specification v1.1.1.
HEALTHCHECK varsayılanları, sandığınızdan daha uzun bir arıza penceresi bırakıyor
Dockerfile referansındaki varsayılanlar şöyle: --interval=30s, --timeout=30s, --start-period=0s, --start-interval=5s, --retries=3. Konteynerin unhealthy sayılması için ardışık retries kadar başarısızlık gerekiyor (bkz. Docker, Dockerfile reference).
Varsayılanlarla hesabı yapın: bir servis yanıt vermemeye başladığında, 30 saniyelik aralık ve 3 denemeyle unhealthy damgası yaklaşık 90 saniye sonra düşüyor. Timeout'un da 30 saniye olduğunu ekleyin; kontrol komutu asılı kalıyorsa süre daha da uzuyor. Yük dengeleyicinizin arkasında bu, iki dakikaya varan hatalı trafik demek.
Üç ayrıntı daha:
- Timeout aşılırsa kontrol süreci
SIGKILLile durduruluyor. - Çıkış kodu
0sağlıklı,1sağlıksız;2ayrılmış, kullanmayın (bkz. Docker, Dockerfile reference). - Start period içindeki başarısızlıklar retry sayacına yazılmıyor. Ama start period içinde bir kez başarılı olunursa, sonraki tüm başarısızlıklar sayılmaya başlıyor.
--start-interval Docker Engine 25.0 ve sonrasını gerektiriyor. Sağlık kontrolü çıktısının yalnızca ilk 4096 baytı saklanıyor, yani hata ayıklama için uzun çıktı basmak işe yaramıyor.
Web uygulaması için makul bir başlangıç:
HEALTHCHECK --interval=10s --timeout=3s --start-period=30s --start-interval=2s --retries=3 \
CMD curl -fsS https://ininia.com/up || exit 1
Sağlık ucu veritabanına dokunmasın. Veritabanı yavaşladığında tüm uygulama konteynerlerinin birden unhealthy olup yeniden başlatılması, yavaşlığı kesintiye çevirir.
depends_on: service_healthy sıralamayı çözer, yeniden bağlanmayı çözmez
Compose'un depends_on koşulları üç tane: service_started, service_healthy ve service_completed_successfully. İkincisi spesifikasyonda açık: bağımlılığın, bağımlı servis başlamadan önce healthcheck'e göre sağlıklı olması bekleniyor (bkz. Compose file reference, services).
Buradaki yaygın hata, bunu çalışma zamanı garantisi sanmak. service_healthy yalnızca başlangıç sırasını düzenliyor. Veritabanı üç saat sonra yeniden başladığında uygulamanız yeniden başlatılmıyor; kendi bağlantı havuzunu yeniden kurmak zorunda. Yeniden bağlanma mantığını uygulamada yazmaktan kaçış yok.
Compose bir yardımcı daha sunuyor: depends_on altında restart: true verirseniz bağımlılık güncellendiğinde Compose bu servisi yeniden başlatıyor. required: false ise bağımlılık başlatılamadığında hata yerine yalnızca uyarı üretiyor.
services:
app:
build: .
depends_on:
db:
condition: service_healthy
restart: true
db:
image: postgres:18
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 3s
retries: 10
start_period: 20s
Compose sürümlemesi hakkında bir uyarı: proje artık "v2" değil. v2.x serisinden doğrudan v5.0.0'a geçildi (v5.0.0: 2 Aralık 2025) ve v3/v4 tag'leri hiç yayımlanmadı. CI betiğinizde sürüm kontrolü yapıyorsanız bu atlamayı hesaba katın.
Build süresini belirleyen şey katman sırası, cache mount değil (ama ikisi de gerekli)
Bağımlılık kurulumunu kaynak kodun kopyalanmasından önce yapmazsanız, her commit tüm bağımlılıkları yeniden indirir. Bunun üstüne BuildKit'in cache mount'u indirilen paketleri build'ler arasında saklar.
RUN --mount=type=cache seçenekleri ve varsayılanları: id (varsayılan: target değeri), target, ro, sharing (shared | private | locked, varsayılan shared), from, source, mode (varsayılan 0755), uid ve gid (varsayılan 0).
PHP tarafında tipik bir çok aşamalı build:
# syntax=docker/dockerfile:1
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN --mount=type=cache,target=/tmp/composer,sharing=locked \
COMPOSER_CACHE_DIR=/tmp/composer \
composer install --no-dev --no-scripts --prefer-dist --no-interaction
FROM php:8.4-fpm-alpine AS runtime
WORKDIR /var/www/html
COPY --from=vendor /app/vendor ./vendor
COPY . .
RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app \
&& chown -R app:app storage bootstrap/cache
USER app
HEALTHCHECK --interval=10s --timeout=3s --start-period=30s --retries=3 \
CMD php -r 'exit(@file_get_contents("https://ininia.com") === false ? 1 : 0);'
sharing=locked seçimi kasıtlı: paralel build'lerde aynı Composer cache dizinine iki sürecin birden yazması bozuk arşivler üretiyor. Docker'ın kendi en iyi uygulamalar sayfasındaki klasik kural da geçerli: apt-get update her zaman apt-get install ile aynı RUN içinde olmalı, yoksa cache geçersizleşmesi paket listesini bayatlatıyor.
.dockerignore build context'ini küçültmenin en ucuz yolu. Dosya, context'in kök dizininde aranıyor ve eşleşen dosyalar builder'a gönderilmeden önce context'ten çıkarılıyor. .git, node_modules, vendor ve storage/logs listede yoksa her build'de yüzlerce megabayt boşuna aktarılıyor.
Alpine'ı seçmeden önce musl'ın DNS davranışını bilin
Alpine imajları küçük, ama musl libc'nin glibc'den farklı davrandığı yerler var ve DNS en çok ısıranı. musl'ın resolver'ı yapılandırılmış nameserver'ların hepsini paralel sorguluyor ve ilk gelen yanıtı kabul ediyor; glibc bunları sırayla dener. Bir nameserver yanlış yapılandırılmışsa, glibc'de fark etmeyeceğiniz bir sorun musl'da rastgele başarısızlığa dönüşüyor.
İkinci fark daha somut: musl 1.2.4'e kadar DNS over TCP desteklemiyordu. Yani 512 bayttan büyük DNS yanıtları (çok sayıda A kaydı, uzun arama alan adı zinciri) kesiliyordu. musl 1.2.4 (Mayıs 2023) bu desteği ekledi (bkz. musl libc, functional differences from glibc). Temel imajınızın musl sürümünü kontrol edin; eski bir Alpine tabanı kullanıyorsanız bu hâlâ sizin sorununuz.
Üçüncü fark search davranışı: ndots eşiğini karşılayan sorgular yalnızca global ad alanında deneniyor ve glibc'nin yaptığı gibi arama listesine düşülmüyor. Kubernetes içinde kısa servis adlarıyla çalışan kod bu yüzden Alpine'da farklı davranabiliyor.
Karar kuralı: statik derlenmiş Go ikili dosyaları için Alpine (hatta scratch) doğru seçim. PHP, Python veya Node gibi C kütüphanelerine bağımlı yığınlarda, boyut farkını kazanmak için aldığınız riski ölçün. -slim Debian tabanlı imajlar çoğu ekip için daha iyi bir denge.
Yeniden başlatma değil, yeniden dağıtım: sıfır kesintiyi ne sağlıyor?
Tek sunucuda Compose ile çalışıyorsanız docker compose up -d eski konteyneri durdurup yenisini başlatır; arada kesinti olur. Kesintisiz geçiş için gereken üç şey:
- Trafiği yöneten bir katman. Nginx ya da Traefik önde dursun; uygulama konteyneri doğrudan 80/443 dinlemesin.
- Yeni sürümü hazır olmadan trafiğe sokmama. Sağlık kontrolü geçene kadar yeni konteynere istek gitmemeli. Compose tek başına bunu yapmıyor; ya bir proxy'nin sağlık kontrolüne bağlayın ya da dağıtım betiğinizde bekleyin.
- Zarif kapanma. Eski konteyner
SIGTERMaldığında mevcut istekleri bitirmeli. PHP-FPM ve çoğu uygulama sunucusu bunu destekliyor, amastop_grace_periodvarsayılanı yetmeyebilir; uzun süren isteğiniz varsa artırın.
Veritabanı migration'ları bu akışın en kırılgan yeri. Kural: migration'lar geriye uyumlu olmalı. Sütun silmek iki dağıtıma bölünür — önce kod sütunu kullanmayı bırakır, sonraki dağıtımda sütun silinir. Aksi hâlde eski ve yeni konteynerin bir arada çalıştığı birkaç saniyede hata alırsınız.
Kaynak sınırları ve log: sessizce sunucu dolduran iki kalem
Compose spesifikasyonunda deploy.resources.limits altındaki alanlar cpus, memory ve pids. Spesifikasyon limits için "platform konteynerin daha fazlasını ayırmasını engellemeli", reservations için "platform konteynerin en az yapılandırılan miktarı ayırabileceğini garanti etmeli" diyor. Bellek sınırı koymamak, tek bir kaçak sürecin sunucudaki tüm servisleri OOM ile düşürmesi demek.
Log tarafında varsayılan JSON dosya sürücüsü sınırsız büyüyor. Disk dolduğunda uygulama değil, sunucudaki her şey birden durur; bu yüzden log rotasyonu her serviste tanımlanmalı:
services:
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 1g
cpus: "1.5"
Üretim kontrol listesi
Dağıtımdan önce bu on maddeyi işaretleyin:
- Çok aşamalı build kullanılıyor; son imajda derleyici ve build araçları yok.
USERtanımlı; konteyner root olarak çalışmıyor..dockerignoreiçinde en az.git, bağımlılık dizinleri ve log dizinleri var.- Temel imaj etiketi sabit (
php:8.4-fpm-alpinegibi),latestdeğil. HEALTHCHECKtanımlı ve veritabanına dokunmuyor.- Log sürücüsünde
max-sizevemax-fileayarlı. deploy.resources.limitsaltında bellek sınırı var.- Migration'lar geriye uyumlu; sütun silme iki dağıtıma bölünmüş.
- Gizli bilgiler imaja gömülmemiş; ortam değişkeni veya secret olarak veriliyor.
- Geri alma (rollback) yolu test edilmiş: önceki imaj etiketi elde ve çalıştırılabilir.
Dokuzuncu madde en sık atlananı. Build sırasında ARG ile verilen bir token, imaj katmanlarında kalıyor ve docker history ile okunabiliyor. Gizli bilgi build zamanında gerekiyorsa RUN --mount=type=secret kullanın.
Sırada ne var
Elinizdeki Dockerfile'ı açın ve tek bir şeye bakın: bağımlılık kurulumu, kaynak kodun kopyalanmasından önce mi geliyor? Değilse build sürenizin büyük bölümü boşa gidiyor ve bunu düzeltmek on dakikalık iş. Ardından docker compose config çıktısında bellek sınırı ve log rotasyonu olmayan servisleri arayın.
Konteyner altyapısı ve CI/CD tarafındaki yaklaşımımızı DevOps ve altyapı sayfasında, Docker özelinde Docker sayfasında bulabilirsiniz. Mevcut bir kurulumun modernizasyonu için altyapı modernizasyonu vakasına bakabilir, kapsam için iletişim sayfasından yazabilirsiniz.
Varsayılan değerler ve davranışlar 25 Eylül 2026 tarihinde resmî kaynaklardan doğrulanmıştır: Dockerfile referansı (HEALTHCHECK seçenekleri, RUN --mount=type=cache), Compose dosya referansı ve compose-spec (depends_on, deploy.resources), Docker build context dokümanı, Docker Engine 29 sürüm notları, Docker Compose GitHub yayınları, musl libc "functional differences from glibc" sayfası ve musl WHATSNEW. Kod örnekleri kendi yığınınıza uyarlanmadan üretime alınmamalıdır.