docker ps yazıyorsunuz ve STATUS sütununda hiç durmayan bir sayaç görüyorsunuz: Restarting (137) 3 seconds ago. Birkaç saniye sonra komutu tekrar çalıştırıyorsunuz, sayaç sıfırlanmış. Konteyner ayakta görünüyor ama uygulamaya bağlanamıyorsunuz; portu açtığınız hâlde tarayıcı bağlantı reddediyor. Bu, Docker'ın en can sıkıcı arıza biçimidir çünkü sistem "çalışıyor" gibi davranır: süreç var, isim var, kayıt var — sadece uygulama yok.
Buradaki asıl tehlike zamanı yanlış yerde harcamaktır. Çoğu kişi doğrudan Dockerfile dosyasını kurcalamaya, imajı yeniden derlemeye ya da rastgele ortam değişkeni eklemeye başlar. Oysa Docker size ne olduğunu zaten söylüyor; sadece cevap üç ayrı komuta bölünmüş durumda. docker ps size durumu, docker logs uygulamanın son sözünü, docker inspect ise ölüm nedenini sayısal olarak verir. Bu üçünü doğru sırayla okuduğunuzda çoğu restart döngüsü beş dakikada kapanır.
Bu rehberde teşhis zincirini adım adım kuracağız: durum satırını okumaktan çıkış kodunu bulmaya, çıkış kodunun ne anlattığından o koda özel çözüme kadar. Ayrıca restart policy'nin sorunu neden çözmediğini — yalnızca gizlediğini — ve döngüyü kasten durdurup konteynerin içine girmenin nasıl yapıldığını göreceksiniz. Docker'a yeni başladıysanız temel kavramlar için Docker nedir rehberine göz atmanız yeterli olur.
Restarting Durumu Tam Olarak Ne Anlatıyor?#
Bir konteynerin "sürekli yeniden başlaması" aslında tek bir olay değil, üst üste binen iki olaydır: konteynerin içindeki ana süreç (PID 1) sonlanır, Docker daemon da tanımlı restart policy gereği konteyneri yeniden başlatır. Yani Docker bir hata üretmiyor; verdiğiniz talimatı harfiyen uyguluyor. Uygulama her açılışta aynı sebeple ölüyor, Docker her seferinde aynı şekilde ayağa kaldırıyor.
STATUS sütunundaki parantez içi sayı, konteynerin son çıkış kodudur ve teşhisin en değerli parçasıdır:
# Ölü ve yeniden başlayan konteynerler dahil hepsini listele
docker ps -a
# Sadece isim, durum ve imaj
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
Tipik bir çıktı şöyledir:
NAMES STATUS IMAGE
api Restarting (137) 4 seconds ago myapp:1.4
worker Restarting (1) 12 seconds ago myapp:1.4
db Up 6 days (healthy) postgres:16
Burada api ile worker aynı belirtiyi gösteriyor ama nedenleri tamamen farklı: biri dışarıdan öldürülüyor, diğeri kendi kendine hata verip çıkıyor. Aynı belirtiye aynı çözümü uygulamaya çalışmak, bu tür arızalarda en çok vakit kaybettiren yaklaşımdır.
Docker'ın yeniden başlatmaları anlık değildir; ardışık başarısızlıklarda gecikme kademeli olarak artar, yani ilk denemeler saniyenin altındayken ilerleyen denemeler dakikada bire kadar seyrelir. Bu yüzden sabah kurduğunuz bir konteyner öğleden sonra "arada bir deniyor" gibi görünebilir. Döngünün yavaşlaması sorunun hafiflediği anlamına gelmez.
Teşhis Zinciri: ps, logs, inspect#
Restart döngülerinde işe yarayan tek yaklaşım, sabit bir sırayla ilerlemektir. Aşağıdaki üç adım neredeyse her vakada sonucu verir.
1. Adım — Logları okuyun. Konteyner öldüğü için docker logs boş dönmez; Docker geçmiş çalıştırmanın çıktısını saklar. Zaman damgası eklemek, hangi satırın son açılışa ait olduğunu ayırt etmenizi sağlar:
# Son 80 satır, zaman damgalı
docker logs --timestamps --tail 80 api
# Son 5 dakikadaki her şey
docker logs --since 5m api
# Döngüyü canlı izlemek için: yeni denemeleri anında görürsünüz
docker logs -f api
Uygulamanın son yazdığı satır çoğu zaman doğrudan cevaptır: connection refused, permission denied, no such file or directory, bind: address already in use. Log tamamen boşsa bu da bir bilgidir — uygulama tek satır bile yazamadan ölmüş demektir ki bu genelde komut ya da dosya bulunamaması veya anında öldürülme anlamına gelir.
2. Adım — Çıkış kodunu ve OOM bayrağını okuyun. docker inspect bize logların anlatamadığını söyler: konteyner kendi mi çıktı, yoksa öldürüldü mü?
docker inspect api --format '
ExitCode : {{.State.ExitCode}}
OOMKilled: {{.State.OOMKilled}}
Error : {{.State.Error}}
Restarts : {{.RestartCount}}
Policy : {{.HostConfig.RestartPolicy.Name}}
MemLimit : {{.HostConfig.Memory}}'
3. Adım — Kodu tabloya bakarak yorumlayın. Sonraki bölümdeki tablo, çıkış kodunu doğrudan yapılacak işe çevirir.
Bu üç adımın kısa sürmesinin nedeni şudur: docker inspect yalnızca son çalıştırmanın durumunu gösterir, ama RestartCount toplam deneme sayısını verir. RestartCount yüksek ama ExitCode sıfırsa, uygulamanız hata vermeden ama beklenmedik biçimde sonlanıyor demektir — genelde ön planda çalışması gereken bir servisin arka plana geçmesi bu tabloyu üretir.
Çıkış Kodları Tablosu: Hangi Kod Ne Demek?#
Docker'ın çıkış kodları Linux kabuk sözleşmesini izler. 128'in üstündeki kodlar bir sinyalle öldürülmeyi ifade eder: koddan 128 çıkarırsanız sinyal numarasını bulursunuz.
| Kod | Anlamı | İlk bakılacak yer | Tipik çözüm |
|---|---|---|---|
0 | Süreç normal sonlandı | Uygulamanın ön planda kalıp kalmadığı | Servisi foreground modda çalıştırın |
1 | Genel uygulama hatası | docker logs son satırlar | Yapılandırma, bağlantı, eksik değişken |
2 | Kabuk kullanım hatası | Entrypoint script'i | Hatalı parametre veya sözdizimi |
125 | Docker komutunun kendisi başarısız | docker run satırı | Geçersiz bayrak, çakışan port veya isim |
126 | Komut çalıştırılamadı | Dosya izinleri | chmod +x, satır sonu (CRLF) düzeltmesi |
127 | Komut bulunamadı | CMD ve ENTRYPOINT yolu | Yol yanlış ya da paket imajda yok |
137 | SIGKILL ile öldürüldü (128+9) | Bellek limiti, OOM | Limiti yükseltin veya sızıntıyı bulun |
139 | SIGSEGV, segmentasyon hatası (128+11) | Uygulama ve kütüphane uyumu | Farklı taban imaj veya sürüm |
143 | SIGTERM ile durduruldu (128+15) | Kapatma isteği | Genelde normaldir, döngü yapmaz |
Bu tabloyu ezberlemenize gerek yok; ama 137, 1 ve 127 üçlüsünü tanımak restart vakalarının büyük kısmını kapsar. Sırayla ele alalım.
Exit Code 137: Konteyner Öldürüldü, Kendi Çıkmadı#
137 gördüğünüzde uygulamanız hata vermemiştir — dışarıdan öldürülmüştür. En yaygın nedeni bellek sınırının aşılmasıdır. Doğrulaması tek satırdır:
docker inspect api --format '{{.State.OOMKilled}}'
Sonuç true ise konteyner, kendisine tanımlı bellek limitine dayandığı için çekirdek tarafından sonlandırılmıştır. false ise ölüm başka bir yerden gelmiştir: ana makinenin genel belleği tükenmiş, bir orkestratör konteyneri kapatmış ya da bir kapatma script'i kill -9 uygulamış olabilir. Ana makine tarafını çekirdek loglarından doğrulayın:
# Çekirdek OOM kayıtları: hangi süreç öldürüldü?
dmesg -T | grep -iE 'killed process|out of memory'
# systemd tabanlı sistemlerde
journalctl -k --since '30 min ago' | grep -i oom
Konteynerin gerçek tüketimini canlı görmek için docker stats en hızlı araçtır. MEM USAGE / LIMIT sütununda kullanım limitin dibine yaklaşıp sonra konteyner kayboluyorsa teşhis kesinleşmiştir:
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}'
Çözüm iki yönlüdür ve ikisini de yapmanız gerekir. Kısa vadede limiti gerçekçi bir değere çekin:
# Çalışan konteynerin limitini yeniden oluşturmadan güncelle
docker update --memory 1g --memory-swap 1g api
Compose kullanıyorsanız limiti dosyada kalıcı hâle getirin:
services:
api:
image: myapp:1.4
deploy:
resources:
limits:
memory: 1g
reservations:
memory: 512m
Uzun vadede ise şunu sormalısınız: uygulama gerçekten bu kadar belleğe mi ihtiyaç duyuyor, yoksa sızdırıyor mu? Limiti sürekli yükseltmek, sızıntıyı ana makinenin tamamına yayma riskidir; o noktada konteyner değil sunucu düşer. Ana makinedeki bellek tablosunu okumak için Linux bellek kullanımını free ile analiz etme yazısı işinizi görür. Sunucunun toplam belleği gerçekten yetmiyorsa swap alanı oluşturmak döngüyü geçici olarak yumuşatır — ama swap bir çözüm değil, nefes alma payıdır.
Exit Code 1: Uygulama Kendi Hatasıyla Çıkıyor#
1 en dürüst koddur: uygulama açıldı, bir şeyi beğenmedi ve kendi isteğiyle çıktı. Cevap neredeyse her zaman logların son üç satırındadır. Pratikte en sık karşılaşılan üç senaryo şudur.
Veritabanı henüz hazır değil. Compose ile birden fazla servis kaldırdığınızda depends_on yalnızca konteynerin başlamasını bekler, servisin hazır olmasını değil. Uygulama bağlanamayınca çıkar, Docker yeniden başlatır, veritabanı bu arada hazır olur ve döngü kendiliğinden biter — ama bazen bitmez. Doğrusu koşula bağlı bekleme kurmaktır:
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
api:
image: myapp:1.4
depends_on:
db:
condition: service_healthy
Çok servisli kurulumların ayrıntısı için Docker Compose kullanımı rehberine bakabilirsiniz.
Eksik ortam değişkeni. .env dosyasının okunmadığı ya da değişkenin yanlış isimle tanımlandığı durumlarda uygulama çoğu zaman tek satır hata basıp çıkar. Konteynerin gerçekte hangi değişkenleri gördüğünü doğrudan sorun:
docker inspect api --format '{{range .Config.Env}}{{println .}}{{end}}'
İzin sorunu. Konteyner içindeki kullanıcı, bağlanan dizine yazamadığında uygulama açılışta çöker. Bind mount ve volume izinlerinin nasıl hizalanacağı Docker volume ile veri yönetimi yazısında ayrıntılı ele alınıyor.
Exit Code 126 ve 127: Komut Hiç Çalışamadı#
Bu iki kod, uygulamanın kodu bile çalışmadan işin bittiğini söyler. Log çıktısı genelde bomboştur, bu yüzden acemi gözle en kafa karıştırıcı vakalardır.
127 "komut bulunamadı" demektir. En sık üç nedeni vardır: CMD içindeki yol yanlıştır, çalıştırılabilir dosya imaja hiç kopyalanmamıştır ya da kullanılan araç taban imajda mevcut değildir. Alpine tabanlı imajlarda bash bulunmaz (yalnızca sh vardır) ve #!/bin/bash ile başlayan bir script tam olarak bu kodu üretir.
126 ise "komut bulundu ama çalıştırılamadı" demektir. Genelde iki sebepten olur: dosyada çalıştırma izni yoktur veya dosya Windows satır sonlarıyla kaydedilmiştir. CRLF durumunda yorumlayıcı satırının sonuna görünmez bir karakter eklenir ve çekirdek olmayan bir yorumlayıcı arar.
# Komut imajın içinde gerçekten var mı?
docker run --rm myapp:1.4 which entrypoint.sh
# İzinlere ve satır sonuna bak
docker run --rm myapp:1.4 sh -c 'ls -l /app/entrypoint.sh; head -1 /app/entrypoint.sh | od -c | head -2'
Kalıcı çözüm Dockerfile tarafındadır:
COPY entrypoint.sh /app/entrypoint.sh
RUN sed -i 's/\r$//' /app/entrypoint.sh && chmod +x /app/entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]
Bu tür sorunları en baştan engellemek için Dockerfile en iyi pratikleri yazısındaki kurallar iyi bir referanstır.
Restart Policy Sorunu Çözmez, Yalnızca Gizler#
--restart always ya da restart: unless-stopped çok faydalı ayarlardır; sunucu yeniden başladığında servislerin kendiliğinden ayağa kalkmasını sağlarlar. Ama bir arıza durumunda aynı ayar, sorunu görünmez kılan bir perde hâline gelir: uygulama sürekli çöküyor olmasına rağmen docker ps çıktısında konteyner hep "var" görünür ve izleme sisteminiz aksaklığı fark etmeyebilir.
| Policy | Davranış | Ne zaman uygun |
|---|---|---|
no | Hiç yeniden başlatmaz | Hata ayıklama, tek seferlik işler |
on-failure[:n] | Sadece sıfır dışı çıkışta, en fazla n kez dener | Toplu işler, cron benzeri görevler |
always | Her durumda başlatır, daemon yeniden başlarsa da | Kritik servisler |
unless-stopped | always gibi, ama elle durdurulduysa dokunmaz | Üretimde en yaygın tercih |
Sonsuz döngüyü sınırlamak için on-failure:5 çoğu senaryoda daha dürüst bir tercihtir: uygulama beş kez üst üste çökerse konteyner ölü kalır, izleme sisteminiz alarm üretir ve siz sorunu gerçekten öğrenirsiniz. always ile sessizce dönüp duran bir konteyner, saatlerce fark edilmeyen bir kesinti demektir.
Döngüyü Durdurup Konteynerin İçine Bakmak#
En zorlu vaka, konteynerin docker exec çalıştıramayacak kadar kısa yaşamasıdır. Komutu yazana kadar konteyner çoktan ölmüş olur. Çözüm, döngüyü kasten kırmaktır.
Önce policy'yi kapatın ki Docker yeniden başlatmayı bıraksın:
docker update --restart=no api
docker stop api
Ardından aynı imajı, aynı ayarlarla ama farklı bir komutla çalıştırın. Böylece sorunlu uygulama hiç başlamaz, siz de dosya sistemini rahatça gezersiniz:
# Uygulamayı atlayıp doğrudan kabuk aç
docker run --rm -it --entrypoint sh myapp:1.4
# Kabuğun içinde: dosyalar yerinde mi, izinler doğru mu?
ls -l /app
cat /app/config.yml
Ortam değişkenleri ve ağ bağlantılarıyla birlikte test etmek istiyorsanız Compose'un tek seferlik çalıştırma modu bunu tek satıra indirir:
docker compose run --rm --entrypoint sh api
Konteyner çoktan ölmüşse ve içindeki bir dosyaya bakmanız gerekiyorsa, ölü konteynerden bile dosya kopyalayabilirsiniz:
docker cp api:/app/logs/error.log ./error.log
Ölüm anlarını canlı yakalamak için docker events beklenmedik ölçüde kullanışlıdır; hangi konteynerin ne zaman öldüğünü akış hâlinde gösterir:
docker events --filter 'event=die' --filter 'event=oom'
Ağ tarafından kaynaklanan başlangıç hatalarını ayıklarken konteynerlerin hangi ağda olduğunu ve birbirlerine isimle erişip erişemediğini de doğrulamak gerekir; bunun için Docker network yapılandırma yazısı iyi bir başlangıç noktasıdır.
Kalıcı Önlemler: Bir Daha Aynı Yere Düşmemek#
Döngüyü kırmak ilk adımdır; ikinci adım aynı arızanın sessizce tekrarlamasını engellemektir.
- Bellek limitini her zaman tanımlayın. Limitsiz konteyner, sorun çıktığında tüm sunucuyu tüketir; limitli konteyner ise yalnızca kendi ölür. Limitli olan çok daha iyi bir arızadır.
- Log boyutunu sınırlayın. Sürekli yeniden başlayan bir konteyner, saatler içinde gigabaytlarca log üretip diski doldurabilir.
/etc/docker/daemon.jsoniçine döndürme kuralı ekleyin:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
Ayarı uygulamak için systemctl restart docker gerekir ve kural yalnızca yeni oluşturulan konteynerlere işler; mevcut olanları yeniden yaratmanız gerekir. Sunucu genelinde log yönetimi için logrotate ile log yönetimi yazısı tamamlayıcıdır.
- Healthcheck ekleyin ama gerçekçi tutun. Çok kısa bir
start_perioddeğeri, ağır açılan bir uygulamayı daha ayağa kalkamadan sağlıksız ilan eder ve orkestratör onu öldürür — restart döngüsünün fark edilmesi en zor sebeplerinden biri budur. RestartCountdeğerini izleyin. Bu sayı sessizce artıyorsa, "çalışıyor" görünen bir servis aslında sürekli düşüyordur.
Teşhis alışkanlığını bir kez kurduğunuzda restart döngüleri korkutucu olmaktan çıkar: durum satırındaki sayı size nereye bakacağınızı, loglar ise ne yapacağınızı söyler. Geri kalanı sabırla o iki bilgiyi doğru sırayla okumaktır.
Sıkça Sorulan Sorular#
Docker container neden sürekli restart oluyor?#
Konteynerin içindeki ana süreç sonlandığı ve tanımlı restart policy bunu her seferinde yeniden başlattığı için. Yani Docker arızalı değil, talimatı uyguluyor. Gerçek sebep uygulamanın neden öldüğünde saklıdır: bellek limitinin aşılması, eksik ortam değişkeni, hazır olmayan veritabanı bağlantısı veya bulunamayan bir başlangıç komutu en yaygın nedenlerdir. docker ps çıktısındaki parantez içi çıkış kodu size hangi grupta olduğunuzu söyler.
Exit code 137 ne anlama geliyor?#
137, sürecin SIGKILL sinyaliyle öldürüldüğü anlamına gelir; yani 128 artı 9. Uygulama kendi çıkmamış, dışarıdan sonlandırılmıştır. En sık nedeni bellek limitinin aşılmasıdır. docker inspect ad --format '{{.State.OOMKilled}}' komutu true dönerse konteyner bellek sınırına dayandığı için öldürülmüştür. false dönerse ana makinenin genel belleği tükenmiş veya başka bir süreç konteyneri kapatmış olabilir; çekirdek loglarını dmesg ile kontrol edin.
docker logs boş dönüyorsa ne yapmalıyım?#
Boş log, uygulamanın tek satır bile yazamadan öldüğünü gösterir. Bu genellikle komutun hiç çalıştırılamamasıdır: çıkış kodu 126 veya 127 ise başlangıç komutu bulunamamış ya da çalıştırma izni yoktur. docker run --rm -it --entrypoint sh imaj ile aynı imajda kabuk açıp dosyanın gerçekten var olup olmadığını ve izinlerini kontrol edin. Bazı uygulamaların çıktıyı ekrana değil dosyaya yazması da boş log üretir; bu durumda docker cp ile log dosyasını dışarı alın.
Restart policy'yi kapatmak sorunu çözer mi?#
Hayır, ama teşhis için gereklidir. docker update --restart=no ad komutu Docker'ın yeniden başlatmayı bırakmasını sağlar; böylece konteyner ölü kalır ve dosya sistemini, loglarını rahatça inceleyebilirsiniz. Policy'yi kapatmak arızayı gidermez, sadece görünür hâle getirir. Sorun çözüldükten sonra üretim servisleri için unless-stopped ya da sınırlı bir on-failure:5 değerine geri dönmelisiniz.
Konteynerime ne kadar bellek limiti vermeliyim?#
Ölçmeden karar vermeyin. docker stats ile uygulamanın normal yük altındaki gerçek tüketimini birkaç saat izleyin, gördüğünüz tepe değerin yaklaşık yüzde otuz ila elli üzerini limit olarak tanımlayın. Limiti gereğinden yüksek tutmak koruma sağlamaz; gereğinden düşük tutmak ise trafik arttığında 137 hatasını üretir. Kullanım limitin dibine sürekli yaklaşıp hiç geri düşmüyorsa muhtemelen bir bellek sızıntısıyla karşı karşıyasınız; bu durumda limiti yükseltmek sorunu ertelemekten öteye gitmez.
Compose ile başlattığım servis çöküyor, logu da göremiyorum#
docker compose logs --tail 100 servis_adi komutuyla o servise ait geçmiş çıktıyı okuyabilirsiniz; sonuna -f ekleyerek yeniden başlatma denemelerini canlı izleyin. Hâlâ boşsa uygulamayı hiç başlatmadan kabuk açın: docker compose run --rm --entrypoint sh servis_adi. Bu komut aynı ağ, ortam değişkeni ve volume ayarlarıyla bir kabuk verir, böylece bağlantı ve izin sorunlarını uygulamanın gördüğü koşullarda test edebilirsiniz.