docker compose up -d komutu tek bir hata satırı bile üretmeden döndü. docker ps çıktısında konteyner Up 12 seconds yazıyor, port eşlemesi de görünüyor. Ama tarayıcıda sayfa 502 veriyor ya da bağlantı doğrudan reddediliyor. Elinizde SSH oturumu var, konteynerin ayakta olduğunu biliyorsunuz, sorunun nerede olduğuna dair hiçbir fikriniz yok.
Çoğu kişinin refleksi konteyneri silip yeniden kurmaktır. Bazen işe yarar, ama sorunu anlamadığınız için ertesi gün aynı yerden geri döner. Oysa Docker küçük ama fazlasıyla yeterli bir teşhis takımı sunar: docker logs uygulamanın ne söylediğini, docker exec konteynerin içinde gerçekte ne olduğunu, docker inspect ise Docker'ın o konteyneri hangi ayarlarla başlattığını anlatır.
Bu rehberde üç komutu da gerçek arıza senaryolarıyla ele alacağız: canlı log takibi, çökmüş bir konteynerin loglarına ulaşma, exec ile içeri girme, "bash bulunamadı" hatası, attach komutunun neden konteynerinizi durdurabileceği, minimal imajlarda teşhis ve logların diski doldurması. Docker'a yeni başlıyorsanız önce Docker'ın temel kavramlarına göz atmanız buradaki komutları daha oturaklı hale getirir.
Konteyner Ayakta Görünüyor Ama Çalışmıyor: Nereden Başlanır?#
docker ps çıktısındaki Up ifadesi "uygulamam çalışıyor" demek değildir. Sadece konteynerin ana sürecinin (PID 1) hâlâ hayatta olduğunu söyler. Nginx yanlış bir yapılandırma dosyasıyla başlayıp hata verebilir, PHP-FPM veritabanına bağlanamayıp döngüye girebilir; süreç ayakta kaldığı sürece Docker mutludur.
Bu yüzden teşhise her zaman aynı sırayla girin: önce durum, sonra log, sonra içerisi. Durum bilgisini docker ps ve docker inspect verir:
# Duran konteynerler dahil hepsini, okunabilir bir tabloda listele
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
# Tek konteynerin özet sağlık tablosu
docker inspect -f 'durum={{.State.Status}} cikis={{.State.ExitCode}} oom={{.State.OOMKilled}} yeniden={{.RestartCount}}' web
STATUS sütunu üç şeyi ele verir. Up 3 minutes (healthy) yazıyorsa imajda tanımlı bir sağlık kontrolü geçmiş demektir. Up 2 seconds ifadesi her baktığınızda sıfırlanıyorsa konteyner yeniden başlatma döngüsündedir. Restarting (1) 5 seconds ago ise açık bir çökme sinyalidir: uygulama başlıyor, hata verip 1 koduyla ölüyor, restart politikası onu tekrar ayağa kaldırıyor.
RestartCount değerinin sürekli artması log okumanın kaçınılmaz olduğunu gösterir. Çıkış kodu da tek başına güçlü bir ipucudur:
| Çıkış kodu | Anlamı | Tipik sebep |
|---|---|---|
0 | Normal sonlanma | Tek seferlik komut işini bitirdi, uzun ömürlü bir süreç değil |
1 | Uygulama hatası | Eksik ortam değişkeni, hatalı yapılandırma dosyası |
125 | Docker komutu hatalı | Geçersiz bayrak veya seçenek |
126 | Komut çalıştırılamadı | Betik çalıştırılabilir değil (chmod +x eksik) |
127 | Komut bulunamadı | Entrypoint yolu yanlış yazılmış |
137 | SIGKILL ile öldürüldü | Bellek limiti aşıldı (OOM) veya docker kill |
143 | SIGTERM ile durdu | docker stop ile düzgün kapatma |
137 görüp OOMKilled alanı true dönüyorsa arıza uygulamada değil bellek limitindedir; log okumadan önce --memory değerini gözden geçirin.
docker logs ile Canlı Log İzleme#
docker logs konteynerin ana sürecinin stdout ve stderr akışına yazdıklarını gösterir. En sık kullanacağınız kalıp, son satırları alıp canlı takibe geçmektir:
# Son 100 satırı göster, sonra canlı izlemeye geç
docker logs -f --tail 100 web
# Zaman damgalarıyla, sadece son 15 dakika
docker logs -t --since 15m web
# Docker Compose kullanıyorsanız servis adıyla
docker compose logs -f --tail=50 wordpress
Sık kullanılan bayraklar:
| Bayrak | Ne yapar | Örnek |
|---|---|---|
-n, --tail | Son N satırı gösterir | --tail 200 |
-f, --follow | Yeni satırları canlı akıtır | -f |
--since | Belirli andan sonrasını gösterir | --since 30m, --since 2026-08-18T09:00:00 |
--until | Belirli ana kadarını gösterir | --until 10m |
-t, --timestamps | Her satıra zaman damgası ekler | -t |
--since ve --until birlikte kullanıldığında bir olay penceresini kesip alabilirsiniz; "dün gece 03:00'te ne oldu" sorusunda binlerce satır kaydırmaktan çok daha hızlıdır.
Sık düşülen bir tuzak da şu: docker logs boş dönüyorsa bu, konteyner sağlıklı olduğu için değil, uygulamanız log yazmadığı için olabilir. Konteyner dünyasının sözleşmesi, uygulamanın günlüğünü dosyaya değil standart çıkışa yazmasıdır. Uygulamanız kendi içinde /var/log/uygulama.log dosyasına yazıyorsa docker logs sonsuza dek sessiz kalır. Resmi imajlar bu yüzden log dosyalarını /dev/stdout ve /dev/stderr cihazlarına sembolik bağ olarak kurar. Kendi imajınızda aynısını yapabilir ya da geçici olarak dosyayı içeriden okuyabilirsiniz:
docker exec web tail -f /var/log/uygulama.log
Uzun vadeli çözüm uygulamayı standart çıkışa yazacak şekilde yapılandırmaktır; aksi halde log dosyası konteyner silindiğinde onunla birlikte kaybolur.
Konteyner Çöktüyse veya Sürekli Yeniden Başlıyorsa Loglara Nasıl Bakılır?#
En çok ihtiyaç duyulan an, konteynerin artık ayakta olmadığı andır. İyi haber: docker logs durmuş konteynerlerde de çalışır. Konteyner silinmediği sürece kayıtlar diskte durur.
# Çıkmış konteynerleri bul
docker ps -a --filter "status=exited"
# Duran konteynerin son nefesini oku
docker logs --tail 80 -t web
Kritik ayrıntı: docker run --rm ile başlattığınız konteyner çıktığı anda silinir, logları da onunla gider. Teşhis sırasında --rm bayrağını kaldırın; Compose tarafında da down yerine stop kullanın, çünkü down konteynerleri kaldırır.
Yeniden başlatma döngüsündeki bir konteynerde loglar saniyede bir sıfırdan akar. Burada -f yerine sabit bir dilim almak daha okunaklıdır; ayrıca olay akışına bakmak döngünün ritmini gösterir:
# Konteynerin başına ne geldiğini kronolojik izle
docker events --since 30m --filter container=web
# Compose yığınında hangi servis ölüyor
docker compose ps
Uygulama daha ilk saniyede öldüğü için hiçbir şey yazamıyorsa, entrypoint'i geçici olarak devre dışı bırakıp aynı imajın içine kabukla girmek en hızlı yoldur:
docker run --rm -it --entrypoint sh ornek/uygulama:1.4
Bu, imajın içindeki dosyaları ve izinleri uygulama hiç başlamadan incelemenizi sağlar; aynı ortam değişkenleriyle denemek için komuta --env-file .env ekleyin.
docker exec ile Konteynerin İçine Girmek#
docker exec, çalışan bir konteynerin içinde yeni bir süreç başlatır. İnteraktif kabuk için -i (stdin açık) ve -t (sanal terminal) bayrakları birlikte kullanılır:
# İnteraktif kabuk
docker exec -it web bash
# Tek komut çalıştır, çıktısını al, çık
docker exec web nginx -t
# Root olarak gir (imaj root olmayan kullanıcıyla çalışıyorsa)
docker exec -u 0 -it web bash
# Belirli bir dizinde başla
docker exec -w /var/www/html -it wordpress bash
İçeri girdiğinizde ilk bakacağınız üç şey şudur: süreç gerçekten çalışıyor mu (ps aux), servis yerelden cevap veriyor mu (curl -I http://127.0.0.1:8080), beklediğiniz dosya doğru yerde mi (ls -l /etc/nginx/conf.d/).
İki noktaya dikkat edin. Birincisi: exec ile yaptığınız değişiklikler konteynerin yazılabilir katmanında kalır, konteyneri yeniden oluşturduğunuz anda (docker compose up -d --force-recreate ya da imaj güncellemesi) buhar olur. İçeride paket kurup sorunu çözdüyseniz, o değişikliği Dockerfile'a ya da bir hacme taşımadan işi bitmiş saymayın. İkincisi: exec bir kabuk içinde çalışmadığı için zincirleme komutlar beklediğiniz gibi davranmaz.
# YANLIS: && ifadesini host kabugu yorumlar, ikinci komut sunucuda calisir
docker exec web apt-get update && apt-get install -y procps
# DOGRU: tum zinciri konteynerin kabuguna teslim edin
docker exec -u 0 web sh -c "apt-get update && apt-get install -y procps"
Aynı kural yönlendirme ve joker karakterler için de geçerlidir; >, | ve * ifadelerinin konteyner içinde işlenmesini istiyorsanız komutu sh -c "..." içine alın.
bash Yok Hatası: "executable file not found in $PATH"#
docker exec -it redis bash yazdığınızda karşınıza şuna benzer bir satır çıkabilir:
OCI runtime exec failed: exec failed: unable to start container process:
exec: "bash": executable file not found in $PATH: unknown
Bu hata konteynerin bozuk olduğunu değil, o imajda bash bulunmadığını söyler. İmajları küçültmek için Alpine Linux tabanı yaygın olarak kullanılır ve Alpine'da BusyBox'ın sağladığı sh vardır, bash yoktur. Çözüm basitçe kabuğu değiştirmektir:
docker exec -it redis sh
| İmaj ailesi | Örnekler | İçindeki kabuk |
|---|---|---|
| Debian/Ubuntu tabanlı | wordpress, nginx, php, mariadb | bash ve sh |
| Alpine tabanlı | redis:alpine, node:alpine, alpine | Sadece sh (BusyBox ash) |
| BusyBox | busybox | Sadece sh |
| Distroless / scratch | Derlenmiş Go veya Java ikilileri | Kabuk yok |
Distroless ve scratch tabanlı imajlarda hiçbir kabuk, hatta ls bile bulunmaz; bu bilinçli bir güvenlik tercihidir ve saldırganın eline de araç vermez. Böyle bir imajda exec hep aynı hatayla döner; bir sonraki bölümdeki yan konteyner yöntemine geçmeniz gerekir.
Teşhis sırasında gerçekten bir araca ihtiyacınız varsa (ps, ss, dig gibi) geçici olarak kurabilirsiniz, ama bunu kalıcı çözüm saymayın:
# Debian tabanlı imajda
docker exec -u 0 web sh -c "apt-get update && apt-get install -y procps iproute2"
# Alpine tabanlı imajda
docker exec -u 0 redis sh -c "apk add --no-cache procps busybox-extras"
Üretimde tercih edilen yol, teşhis araçlarını imaja hiç koymamak ve yan konteyner kullanmaktır; imaj boyutunun ve saldırı yüzeyinin neden küçük tutulduğunu imaj boyutu optimizasyonu yazısında ayrıntılı bulabilirsiniz.
exec ile attach Arasındaki Fark ve attach'ın Tuzağı#
İki komut yüzeysel olarak benzer görünür, davranışları tamamen ayrıdır. docker exec konteynerde yeni bir süreç başlatır. docker attach ise var olan ana sürecin (PID 1) girdi/çıktı akışına bağlanır; yeni bir kabuk açmaz, zaten çalışan sürecin konsolunu size verir.
docker exec -it | docker attach | |
|---|---|---|
| Ne yapar | Yeni bir süreç başlatır | PID 1'in stdio akışına bağlanır |
| Eşzamanlı kullanım | Aynı anda birden fazla oturum | Bağlananların hepsi aynı ekranı paylaşır |
| Ctrl+C etkisi | Sadece açtığınız süreci sonlandırır | Sinyali PID 1'e iletir, konteyner durur |
| Çıkış yolu | exit yazmak yeterli | Ctrl+P ardından Ctrl+Q |
| Ne zaman kullanılır | Teşhis, dosya inceleme, komut çalıştırma | İnteraktif bir sürece (REPL, konsol) bağlanmak |
attach ile ilgili en pahalı hata, alışkanlıkla Ctrl+C yapıp üretimdeki konteyneri durdurmaktır. Sinyalin iletilmesini engellemek için --sig-proxy=false kullanabilirsiniz:
docker attach --sig-proxy=false web
Ayrıca Ctrl+P Ctrl+Q ayrılma dizisi yalnızca konteyner bir sanal terminalle (-t) başlatılmışsa çalışır. Terminalsiz başlatılmış bir konteynere attach olduysanız ayrılmanın tek yolu terminal penceresini kapatmaktır. Pratik kural: teşhis için her zaman exec; attach'ı yalnızca gerçekten ana sürecin konsoluna ihtiyacınız olduğunda kullanın.
docker inspect ile Ortam Değişkeni, Mount ve Ağ Kontrolü#
Loglar sessizse ve içeride her şey normal görünüyorsa, sorun çoğu zaman konteynerin hangi ayarlarla başladığındadır. docker inspect konteynerin tüm yapılandırmasını JSON olarak döker; -f (Go şablonu) ile istediğiniz parçayı ayıklamak çok daha okunaklıdır.
# Ortam değişkenleri: yanlış veritabanı adresi hatalarının çoğu burada görünür
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' wordpress
# Hangi hacim nereye bağlanmış
docker inspect -f '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}} rw={{.RW}}{{println}}{{end}}' wordpress
# Konteynerin ağı ve IP adresi
docker inspect -f '{{range $ag, $v := .NetworkSettings.Networks}}{{$ag}} = {{$v.IPAddress}}{{println}}{{end}}' wordpress
# Sağlık kontrolünün son çıktısı
docker inspect -f '{{range .State.Health.Log}}{{.Output}}{{end}}' web
Bu çıktılar üç klasik arızayı doğrudan çözer. Veritabanı bağlantı hatalarında ortam değişkenindeki host adı servis adıyla eşleşmiyordur. "Dosyalarım kayboluyor" şikâyetinde Mounts listesi boştur, yani veri kalıcı bir hacme değil konteynerin yazılabilir katmanına yazılmaktadır; kalıcılığın nasıl kurulacağını Docker hacim yönetimi yazısı anlatır. Konteynerler birbirini bulamıyorsa Networks çıktısı ikisinin ayrı ağlarda olduğunu gösterir; isimle haberleşme kuralları için Docker ağ yapılandırması rehberine bakın.
Bir uyarı: docker inspect ortam değişkenlerini düz metin olarak basar. Veritabanı parolası, API anahtarı ne varsa ekrana gelir. Bu çıktıyı bir destek talebine veya paylaşılan bir sohbete yapıştırmadan önce mutlaka temizleyin.
Konteynerde Hiçbir Araç Yokken Teşhis: Yan Konteyner ve docker cp#
Distroless bir imajda ne curl ne ss bulunur. Bu durumda araçları içeri kurmak yerine, aynı ağ ve süreç ad alanını paylaşan geçici bir "araç kutusu" konteyneri başlatırsınız. Böylece hedef konteynere hiç dokunmadan onun gözünden bakabilirsiniz:
# Hedef konteynerin ağını paylaşan geçici teşhis konteyneri
docker run --rm -it --network container:web nicolaka/netshoot
# Ağ ve süreç görünümünü birlikte paylaş
docker run --rm -it --network container:web --pid container:web nicolaka/netshoot
İçeride artık curl 127.0.0.1:8080, ss -lntp, dig veritabani gibi komutlar hedef konteynerin ağ görünümüyle çalışır. Süreç ad alanını da paylaştıysanız ps aux çıktısında hedefin süreçlerini görürsünüz.
Sunucuya root erişiminiz varsa sudo nsenter -t $(docker inspect -f '{{.State.Pid}}' web) -n ss -lntp ile aynı sonuca ara konteyner olmadan ulaşabilirsiniz.
Dosya alıp vermek için docker cp en sade araçtır ve durmuş konteynerlerde bile çalışır — çöken bir konteynerden log dosyasını kurtarmanın en hızlı yolu budur:
# Konteynerden sunucuya
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
# Sunucudan konteynere
docker cp ./nginx.conf web:/etc/nginx/nginx.conf
docker exec web nginx -s reload
Loglar Diski Doldurduğunda: json-file Sürücüsü ve Rotasyon#
Varsayılan log sürücüsü json-file'dır ve varsayılan olarak hiçbir boyut sınırı yoktur. Gürültülü bir uygulama haftalar içinde onlarca gigabaytlık tek bir dosya üretebilir. Üstelik bu dosyalar konteynerin yazılabilir katmanının dışında durduğu için docker ps -s çıktısındaki boyutta görünmezler; disk dolar, sebep bulunamaz.
# Bütün konteynerlerin log boyutunu büyükten küçüğe listele
sudo du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tail
Kalıcı çözüm rotasyonu açmaktır. Sunucu genelinde /etc/docker/daemon.json dosyasına yazılır ve Docker servisi yeniden başlatılır:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
Bu ayar yalnızca yeni oluşturulan konteynerleri etkiler; mevcut olanların yeniden yaratılması gerekir. Servis bazında ayarlamak için Compose dosyanıza ekleyin:
services:
web:
image: nginx:1.27
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Disk hemen şimdi doluysa acil çözüm dosyayı silmek değil sıfırlamaktır — silmek dosya tanıtıcısını açık bırakır ve yer geri gelmez:
sudo truncate -s 0 $(docker inspect -f '{{.LogPath}}' web)
Logları sistem günlüğüne yönlendirmeyi tercih ederseniz journald sürücüsüne geçebilirsiniz; bu durumda docker logs yerine journalctl CONTAINER_NAME=web -f kullanırsınız ve rotasyonu sistem üstlenir. Bu tarafı journalctl ile log yönetimi yazısı ayrıntılandırır.
Belirti, Sebep ve Çözüm Tablosu#
Aşağıdaki tablo, sahada en sık karşılaşılan durumları hangi komutla teşhis edeceğinizi özetler:
| Belirti | Muhtemel sebep | İlk komut |
|---|---|---|
Up ama site 502 veriyor | Uygulama içeride hata verip beklemede | docker logs --tail 100 web |
Restarting döngüsü | Eksik ortam değişkeni veya hatalı yapılandırma | docker logs --tail 50 web ve docker events |
docker logs bomboş | Uygulama standart çıkış yerine dosyaya yazıyor | docker exec web tail -f /var/log/... |
bash: not found | Alpine veya distroless imaj | docker exec -it web sh |
| Konteyner veritabanını bulamıyor | Servisler farklı ağlarda ya da host adı yanlış | docker inspect -f ile .Config.Env |
| Yeniden başlatınca veriler gitti | Kalıcı hacim tanımlı değil | docker inspect -f ile .Mounts |
| Çıkış kodu 137 | Bellek limiti aşıldı (OOM) | docker inspect -f '{{.State.OOMKilled}}' |
Çok servisli bir yığında bu komutları servis adlarıyla kullanmak daha pratiktir; docker compose logs, docker compose exec ve docker compose ps aynı işi Compose dosyanızdaki isimlerle yapar. Servis adlandırma konusunda Docker Compose kullanımı rehberi iyi bir başlangıç noktasıdır. Teşhis ederken komutları hep aynı sırayla deneyin: durum, log, exec, inspect.
Sıkça Sorulan Sorular#
docker exec ile docker attach arasındaki fark nedir?#
docker exec konteynerin içinde tamamen yeni bir süreç başlatır; açtığınız kabuktan exit ile çıkmak konteyneri etkilemez ve aynı anda birden fazla oturum açabilirsiniz. docker attach ise konteynerin zaten çalışan ana sürecinin girdi/çıktı akışına bağlanır. Burada Ctrl+C tuşlaması sinyali doğrudan ana sürece ileteceği için konteyner durur. Teşhis amacıyla neredeyse her zaman exec kullanılmalıdır.
Konteyner çöktükten sonra loglarına hâlâ ulaşabilir miyim?#
Evet. docker logs komutu durmuş konteynerlerde de çalışır, çünkü kayıtlar konteynerin kendisiyle birlikte sunucu diskinde tutulur. Kayıtların kaybolmasının tek yolu konteynerin silinmesidir. Bu yüzden sorun yaşadığınız bir servisi docker run --rm ile başlatmayın ve teşhis sırasında docker compose down yerine docker compose stop tercih edin.
docker exec -it bash komutu neden executable file not found hatası veriyor?#
Çünkü o imajın içinde bash yoktur. Alpine Linux tabanlı imajlarda yalnızca BusyBox'ın sağladığı sh bulunur, bu yüzden docker exec -it <ad> sh çalışır. Distroless ve scratch tabanlı imajlarda ise hiçbir kabuk yer almaz; bu imajlarda teşhis için aynı ağ ad alanını paylaşan geçici bir araç konteyneri başlatmanız ya da dosyaları docker cp ile dışarı almanız gerekir.
docker logs neden hiçbir çıktı vermiyor?#
En yaygın neden, uygulamanın günlüğünü standart çıkış yerine konteyner içindeki bir dosyaya yazmasıdır. docker logs yalnızca stdout ve stderr akışını gösterir. Log dosyasını docker exec <ad> tail -f /yol/dosya.log ile okuyabilirsiniz, ancak kalıcı çözüm uygulamayı standart çıkışa yazacak şekilde yapılandırmaktır. Log sürücüsü olarak none seçilmişse de komut çıktı üretmez.
Konteyner logları diskimi doldurdu, nasıl temizlerim?#
Varsayılan json-file sürücüsünde boyut sınırı yoktur. Acil durumda log dosyasını truncate -s 0 ile sıfırlayın; dosyayı silmek yer kazandırmaz çünkü Docker tanıtıcıyı açık tutmaya devam eder. Kalıcı çözüm için /etc/docker/daemon.json dosyasına max-size ve max-file seçeneklerini ekleyip Docker servisini yeniden başlatın. Bu ayar yalnızca yeni oluşturulan konteynerler için geçerlidir.
Konteynerin içinde yaptığım değişiklikler kalıcı olur mu?#
Hayır. docker exec ile kurduğunuz paketler ve düzenlediğiniz dosyalar konteynerin yazılabilir katmanında durur; konteyneri yeniden oluşturduğunuz veya imajı güncellediğiniz anda kaybolur. Kalıcı olması gereken yapılandırmalar Dockerfile'a, kalıcı olması gereken veriler ise adlandırılmış bir hacme taşınmalıdır. exec ile yapılan müdahaleyi geçici bir yama olarak görün.