Docker Compose'ta bir servisin ana container'ı başlamadan önce veritabanı migration'ı çalıştırmak, volume izinlerini düzeltmek gibi hazırlık işleri için pre_start kullanılır. Compose bu adımları init container olarak modeller: her adım kendi kısa ömürlü container'ında, yazıldığı sırayla çalışır ve servis ancak tüm adımlar 0 koduyla çıkarsa başlar. Container çalıştıktan sonra bir komut çalıştırmak için post_start, durdurulmadan hemen önce bir komut çalıştırmak için pre_stop lifecycle hook'ları vardır.
Bu yazı üç hook'un nasıl çalıştığını, ne zaman yeniden çalıştırıldığını, eski "tek seferlik servis" kalıbıyla farkını ve başlangıç sırasını belirleyen depends_on koşullarını anlatır. Sağlık kontrolü ile depends_on kullanımının temelleri Docker Compose rehberinde var; burada init container ve hook ayrıntılarına odaklanıyoruz. Örneklerdeki Compose dosyaları Docker Compose v5.5.1 ile docker compose config komutundan hatasız geçti. Serinin tamamı için Docker öğrenme rehberi.
Üç hook'a genel bakış

| Hook | Nerede çalışır | Ne zaman | Tipik kullanım |
|---|---|---|---|
| pre_start | Ayrı, kısa ömürlü bir container | Servis container'ı oluşturulduktan sonra, başlamadan önce | Migration, izin düzeltme, yapılandırma üretme |
| post_start | Çalışan servis container'ının içinde | Container başladıktan sonra, zamanı garanti değil | Harici bir kayıt sistemine bildirim |
| pre_stop | Çalışan servis container'ının içinde | Açık bir durdurma komutunda, durdurma sinyalinden önce | Son yedek, kapanış işlemleri |
Belgeler lifecycle hook'ların önemli bir avantajına dikkat çeker: hook'lar özel ayrıcalıklarla, örneğin root kullanıcısıyla çalışabilir; container'ın kendisi ise güvenlik için düşük ayrıcalıklı bir kullanıcıyla çalışmaya devam eder. Böylece yüksek izin gerektiren tek bir iş için bütün servisi root'a çevirmek zorunda kalmazsınız.
pre_start nasıl çalışır?
pre_start listesindeki her adım için şunlar geçerlidir:
- Adım, servis container'ı oluşturulduktan sonra ama başlatılmadan önce yaratılan kendi kısa ömürlü container'ında çalışır.
- Varsayılan olarak servisin imajını kullanır;
imageile başka bir imaj verebilirsiniz. Hook imajı, yenileme aralığı dahil, ana servisinpull_policyayarına uyar. - Servisle aynı network'lere katılır; bu sayede
depends_onile belirtilen servislere ulaşabilir. - Servisin volume bağlamalarını paylaşır; paylaşılan bir volume'a yazdığı dosyalar servis tarafından hemen görülür.
- Bir sonraki adımın ve servisin başlaması için 0 koduyla çıkmalıdır. Sıfır dışındaki bir çıkış kodu servisin ve ona bağımlı her şeyin başlatılmasını durdurur.
Adımlar ancak servisin depends_on koşulları sağlandıktan sonra çalışır. Yani veritabanının sağlıklı olmasını bekleyen bir servisin migration adımı da aynı beklemeden yararlanır. Kullanılabilen alanlar şunlardır: command (seçilen imajın entrypoint'i zaten istenen işi yapıyorsa isteğe bağlı), image, user (verilmezse imajdaki kullanıcı; image yoksa ana servis komutunun kullanıcısı), privileged, working_dir, environment (servis ortamını devralır, ekler veya ezer) ve per_replica.
Adım ne zaman yeniden çalışır?
Bu kural sunucu kullanımı için önemlidir, çünkü migration'ın her up komutunda tekrar çalışıp çalışmayacağını belirler. Mevcut tanımıyla daha önce başarıyla çalışmış bir adım, sonraki docker compose up komutlarında atlanır; servis container'ı restart politikası nedeniyle yeniden başladığında da çalışmaz. Adım şu durumlarda yeniden çalışır: tanımı değiştiyse, önceki çalıştırma başarısız olduysa ya da servis yeniden oluşturulduysa, örneğin servis yapılandırması değiştiğinde veya --force-recreate ile. Restart politikalarının kendisi Docker restart policy yazısında anlatılıyor.
docker compose up -d
# Servisi ve pre_start adımlarını yeniden çalıştırmak için
docker compose up -d --force-recreate appÖrnek: veritabanı hazır olunca migration ve seed
Aşağıdaki dosyada app servisi önce db servisinin sağlıklı olmasını bekler, ardından migration ve başlangıç verisi yükleme adımlarını sırayla çalıştırır. Veritabanı parolası bir secret olarak verilir:
services:
app:
image: myapp:latest
depends_on:
db:
condition: service_healthy
pre_start:
- command: ["./manage.py", "migrate"]
- command: ["./manage.py", "loaddata", "fixtures.json"]
restart: unless-stopped
db:
image: postgres:18
environment:
POSTGRES_USER: app
POSTGRES_DB: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
retries: 5
start_period: 30s
timeout: 10s
secrets:
db_password:
file: ./db_password.txtİki adım da image belirtmediği için myapp:latest imajından oluşturulan geçici container'larda çalışır. Migration başarısız olursa app başlamaz ve hata docker compose up çıktısında raporlanır. İkinci adım başarısız olursa birinci adım geri alınmaz ama app yine başlamaz; yani migration'larınızın tekrar çalıştırılmaya dayanıklı olması iyi bir alışkanlıktır. Sağlık kontrolündeki $$ kullanımı, değişkenin Compose tarafından değil container içindeki kabuk tarafından çözülmesi içindir (bkz. Compose değişken interpolasyonu). Secret kullanımı Compose secrets ve configs yazısında anlatılıyor.
Örnek: root olmayan servis için volume sahipliğini düzeltmek

Belgelerin belirttiği gibi named volume'lar root sahipliğiyle oluşturulur. Servis root olmayan bir kullanıcıyla çalışıyorsa volume'a yazamayabilir. Bunu servisin kendisini root yapmadan, küçük bir imajla root olarak çalışan tek bir adımla düzeltebilirsiniz:
services:
app:
image: myapp:latest
user: "1000:1000"
volumes:
- data:/data
pre_start:
- image: busybox
user: root
command: sh -c 'chown -R 1000:1000 /data'
volumes:
data:Adım busybox imajını kullanır ve root olarak çalışır, servis ise 1000 numaralı kullanıcıyla kalır. Adım servisin volume bağlamalarını paylaştığı için /data aynı volume'dur. Volume'ların kendisi için Docker volume kullanımı yazısına bakın. İlginç bir ayrıntı: docker compose config çıktısında shell biçiminde yazdığımız komut sh, -c ve chown -R 1000:1000 /data olmak üzere üç öğeli bir listeye dönüştü; Compose komutları normalize ederken bu biçimi kullanır.
Tek seferlik servis kalıbı yerine pre_start
pre_start gelmeden önce "Y başlamadan X'i çalıştır" ihtiyacı genellikle ayrı bir servisle çözülürdü: hazırlık işi restart: "no" olan bir servis olarak yazılır, ana servis de ona condition: service_completed_successfully ile bağlanırdı.
services:
migrate:
image: myapp:latest
command: ["./manage.py", "migrate"]
restart: "no"
app:
image: myapp:latest
depends_on:
migrate:
condition: service_completed_successfullyAynı iş pre_start ile tek satırlık bir adıma iner. Belgeler bu yaklaşımın neden tercih edilebilir olduğunu şöyle sıralar: hazırlık işi hemen çıkan eşit düzeyde bir servis değil, servisin alt adımı olarak modellenir; tamamlanan adımlar docker compose ps çıktısında çıkmış servisler olarak görünmez; birden fazla adımı zincirlemek için tek seferlik servisler arasında depends_on ağı kurmak gerekmez; geçici container servisin imajını devraldığı için image: satırını tekrar yazmazsınız.
Tek seferlik servis kalıbı yine de geçerliliğini korur: hazırlık işi birden fazla servisin bağlı olduğu ortak bir işse ya da tek bir servisten bağımsız olarak ele alınması gerekiyorsa ayrı servis daha uygundur.
Ne zaman init container kullanılmaz?
Belgeler iki durumu açıkça ayırır. Statik dosyalar ve sırlar için init container gerekmez; Compose'un configs ve secrets öğeleri bunları hedef yol, izin, UID ve GID ayarlanabilir biçimde doğrudan bağlar. Zamanlanmış yedekler, çıkış sonrası temizlik veya periyodik bakım gibi kendi yaşam döngüsü olan arka plan işleri için de init container yanlış araçtır, çünkü bu işler servisin başlamasından önce değil, ondan bağımsız çalışır.
Bilinen sınırlamalar da şunlardır. pre_start her replika için değil, servis için bir kez çalışır (per_replica: false); belgelere göre replika başına çalıştırma (per_replica: true) henüz desteklenmiyor. Bizim denememizde per_replica: true yazılmış bir dosya docker compose config doğrulamasından hatasız geçti; doğrulamanın geçmesi özelliğin desteklendiği anlamına gelmez. Replikalar arasında paylaşılan bağlamalar (named volume, bind mount) adımdan erişilebilir; tmpfs veya anonim volume gibi her container'a özel bağlamalara tek bir ortak çalıştırma erişemez. Bu bir hata değildir, adım bu bağlamalar olmadan çalışır. Son olarak servisi ölçeklendirmek adımları yeniden tetiklemez.
post_start: container başladıktan sonra
post_start, container başladıktan sonra çalışan komutları tanımlar ama tam olarak ne zaman çalışacağı garanti edilmez; özellikle container'ın entrypoint'i çalışırken hangi noktada devreye gireceği belli değildir. Hook ile entrypoint arasında sıralama garantisi olmadığı için belgeler post_start'ı, uygulama çalışmaya başlamadan önce tamamlanması gerekmeyen işler için önerir. Uygulamanın başlamadan önce ihtiyaç duyduğu her şey pre_start adımına aittir.
Alanlar şunlardır: command (zorunlu, shell veya exec biçimi), user, privileged, working_dir ve environment. Belgelerdeki örnekte container 1001 numaralı kullanıcıyla çalışırken root olarak çalışan bir hook servisi iç kayıt sistemine bildirir; uygulama istekleri karşılamak için kaydın bitmesini beklemez.
pre_stop: durdurmadan hemen önce
pre_stop, container belirli bir komutla durdurulmadan önce çalışan komutlardır: docker compose down veya terminalde Ctrl+C gibi. Container kendi kendine durursa veya aniden sonlandırılırsa bu hook'lar çalışmaz. Hook durdurma sinyali gönderilmeden önce çalıştığı için uygulama hâlâ tam olarak çalışırken bitmesi gereken işler için uygundur. Yapılandırması post_start ile aynıdır.
services:
app:
image: backend
user: "1001"
volumes:
- data:/data
post_start:
- command: /opt/scripts/register-service.sh
user: root
pre_stop:
- command: cp /data/app.db /data/app.db.bak
stop_grace_period: 30s
volumes:
data: {}Bu dosyada iki hook ve bir durdurma ayarı bir arada. stop_grace_period, durdurma sinyalini (varsayılan olarak SIGTERM, stop_signal ile değiştirilebilir) işlemeyen bir container için Compose'un SIGKILL göndermeden önce ne kadar bekleyeceğini belirler; varsayılanı 10 saniyedir. Uygulamanız kapanırken veri yazıyorsa bu süreyi ihtiyacınıza göre ayarlayın. pre_stop hook'unun kendisinin bu süreye nasıl dahil edildiği belgelerde ayrıca açıklanmadığı için uzun süren işleri pre_stop'a koymadan önce kendi ortamınızda deneyin.
depends_on ve başlangıç sırası

Compose container'ları her zaman bağımlılık sırasıyla başlatır ve durdurur. Bağımlılıklar depends_on, links, volumes_from ve network_mode: "service:..." ile belirlenir. Önemli ayrıntı şudur: başlangıçta Compose bir container'ın "hazır" olmasını değil, yalnızca çalışıyor olmasını bekler. Bağlantı kabul etmeden önce kendi servislerini başlatması gereken bir veritabanı için bu yetmez; bu yüzden uzun sözdizimindeki condition alanı kullanılır.
service_started: Kısa sözdizimine eşdeğerdir; bağımlılığın başlamış olması yeterlidir.service_healthy: Bağımlılığınhealthcheckile tanımlanan sağlık kontrolünden geçmesi beklenir.service_completed_successfully: Bağımlılığın başarıyla çalışıp bitmesi beklenir.restart: true: Bağımlılık açık bir Compose işlemiyle, örneğindocker compose restartile güncellenir veya yeniden başlatılırsa bu servis de yeniden başlatılır. Container öldükten sonra çalışma zamanının yaptığı otomatik yeniden başlatmalar buna dahil değildir.required: false: Bağımlılık başlatılmamışsa veya yoksa Compose yalnızca uyarır. Varsayılantruedeğeridir.
services:
web:
image: example/web
depends_on:
db:
condition: service_healthy
restart: true
cache:
condition: service_started
required: false
db:
image: postgres:18
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
cache:
image: redis:latestKısa sözdiziminde yazılmış depends_on bile docker compose config çıktısında condition: service_started ve required: true alanlarıyla normalize edilir; bu, varsayılanların ne olduğunu görmenin pratik bir yoludur. Kaldırma da bağımlılık sırasıyla yapılır: yukarıdaki örnekte web, db ve cache servislerinden önce kaldırılır.
Sık yapılan hatalar
- Migration'ı post_start'a koymak. post_start'ın entrypoint ile sıralama garantisi yoktur; uygulama migration bitmeden başlayabilir. Hazırlık işleri pre_start'a aittir.
- pre_stop'un her durumda çalışacağını sanmak. Container kendisi durursa veya aniden sonlandırılırsa çalışmaz.
- depends_on listesinin hazır olmayı beklediğini sanmak. Kısa sözdizimi yalnızca başlamış olmayı bekler; hazırlık için
service_healthyve birhealthcheckgerekir. - pre_start adımının her up'ta çalışmasını beklemek. Başarılı ve değişmemiş adım atlanır; yeniden çalıştırmak için tanımı değiştirin veya
--force-recreatekullanın. - Servisi yalnızca chown için root çalıştırmak. Bunun yerine root olarak çalışan bir pre_start adımı kullanın.
- Statik ayar dosyası üretmek için init container yazmak. configs ve secrets bu iş için yeterlidir.
Sık Sorulan Sorular
pre_start adımı başarısız olursa ne olur?
Servis ve ona bağlı servisler başlatılmaz, hata docker compose up çıktısında raporlanır. Daha önce başarıyla biten adımlar geri alınmaz.
pre_start adımı ölçeklendirilmiş serviste kaç kez çalışır?
Servis için bir kez. Belgelere göre replika başına çalıştırma henüz desteklenmiyor ve servisi ölçeklendirmek adımı yeniden tetiklemiyor.
post_start ile Dockerfile'daki ENTRYPOINT arasındaki fark nedir?
ENTRYPOINT container'ın ana sürecidir. post_start ise container başladıktan sonra, ana süreçle sıralama garantisi olmadan, farklı bir kullanıcı veya ayrıcalıkla çalışabilen ek bir komuttur. ENTRYPOINT ve CMD ilişkisi için CMD ve ENTRYPOINT farkı yazısına bakın.
Tek seferlik migrate servisini tamamen bırakmalı mıyım?
Zorunlu değil. Hazırlık işi birden fazla servisin ortak ihtiyacıysa veya bağımsız olarak çağrılabilmesi gerekiyorsa ayrı servis kalıbı hâlâ uygundur.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Docker öğrenme rehberi · Docker Compose rehberi · Docker restart policy · Compose secrets ve configs · Docker volume kullanımı · CMD ve ENTRYPOINT farkı