Logs, Shells en Exec: Praktische technieken om draaiende Docker-containers te debuggen
Docker maakt het makkelijk om applicaties te verpakken en te draaien, maar zodra er iets misgaat wil je vooral snel kunnen zien wat er gebeurt, in de container kijken, en ter plekke testen zonder je hele stack omver te trekken. In deze tutorial leer je praktische, herhaalbare technieken om draaiende Docker-containers te debuggen met:
- Logs (wat zegt de applicatie, de runtime, en de container zelf?)
- Shells (hoe krijg je een bruikbare prompt in een minimale container?)
docker exec(hoe inspecteer je processen, netwerk, filesystem en configuratie live?)
We gaan diep: niet alleen “welk commando”, maar ook waarom, wat je kunt verwachten, en valkuilen. Alle voorbeelden zijn echte commando’s die je direct kunt gebruiken.
Inhoud
- Voorbereiding: containers vinden en context opbouwen
- Logs: de snelste route naar oorzaak en tijdlijn
- Shells: een prompt krijgen in minimale images
docker exec: live inspecteren zonder restart- Proces- en resource-debugging in een draaiende container
- Netwerk-debugging: DNS, poorten, routes en connectiviteit
- Filesystem en volumes: wat staat waar, en waarom is het “weg”?
- Geavanceerd: debuggen zonder tools via een “debug sidecar”
- Veelvoorkomende scenario’s en snelle checklists
Voorbereiding: containers vinden en context opbouwen
Begin altijd met: welke container, welke image, welke ports, welke status.
Containers tonen
docker ps
docker ps -a
Handige kolommen (gefilterd):
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
Details van één container
Kies een containernaam (bijv. web) en inspecteer:
docker inspect web
Vaak wil je specifiekere velden:
docker inspect -f '{{.Config.Image}}' web
docker inspect -f '{{.State.Status}}' web
docker inspect -f '{{.State.Pid}}' web
docker inspect -f '{{json .NetworkSettings.Ports}}' web
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}} ({{.Type}}){{"\n"}}{{end}}' web
Waarom dit belangrijk is: veel debugproblemen zijn geen “app-bug”, maar een mismatch in ports, mounts, environment, of een container die constant herstart.
Events: wat gebeurt er in real-time?
Docker events geven je een tijdlijn:
docker events --since 30m
docker events --filter container=web
Let op die, restart, oom, kill, health_status.
Logs: de snelste route naar oorzaak en tijdlijn
Logs zijn meestal je eerste bron. Ze vertellen je:
- startvolgorde (migraties, config laden)
- exceptions/stacktraces
- timeouts naar dependencies
- healthcheck failures
- OOM-kills of crashes
Basis: docker logs
docker logs web
Voor een container die herstart, wil je ook de vorige run:
docker logs --previous web
Tip: bij crash-loops is --previous goud waard: je ziet de output vlak vóór de container opnieuw startte.
Tailen, timestamps, filters en context
Tailen (live volgen):
docker logs -f web
Laat alleen de laatste 200 regels zien:
docker logs --tail 200 web
Met timestamps (belangrijk voor correlatie met externe systemen):
docker logs -f --timestamps web
Logs sinds een bepaald moment:
docker logs --since 10m web
docker logs --since "2026-03-04T10:00:00" web
Combineer met grep voor snelle signalen:
docker logs --since 30m web | grep -iE 'error|exception|fatal|panic|oom'
Valkuil: grep mist soms multiline stacktraces. Gebruik dan context:
docker logs --since 30m web | grep -n "Exception" -A 30 -B 10
Logdrivers en waarom docker logs soms “leeg” is
docker logs werkt alleen goed met bepaalde logdrivers (meestal json-file of journald). Check de logdriver:
docker inspect -f '{{.HostConfig.LogConfig.Type}}' web
Veelvoorkomende drivers:
json-file(default op veel systemen)journaldsyslogfluentdawslogs,gcp, etc.
Als de driver bijvoorbeeld syslog is, kan docker logs weinig of niets tonen. Dan moet je in het logdoel kijken, bv. systemd journal:
journalctl -u docker.service --since "10 min ago"
journalctl --since "10 min ago" | grep web
Of bij syslog:
sudo tail -n 200 /var/log/syslog
sudo tail -n 200 /var/log/messages
Logformaten: JSON, multiline en stacktraces
Sommige apps loggen JSON per regel. Dan kun je met jq filteren:
docker logs --since 30m web | jq -r 'select(.level=="error") | .msg'
Als logs niet valide JSON zijn (bijv. stacktrace regels), dan faalt jq. In dat geval kun je eerst filteren op regels die met { beginnen:
docker logs web | grep '^{.*}' | jq -r '.msg'
Multiline-probleem: Docker behandelt logs als regels; stacktraces kunnen “versplinteren”. De beste remedie is applicatie-side (structured logging), maar tijdens debugging helpt het om rond een unieke foutregel te zoeken met -A/-B.
Shells: een prompt krijgen in minimale images
Een shell is je “microscoop”: je kijkt naar bestanden, processen, DNS, sockets, config. Maar containers zijn vaak minimaal: geen bash, geen curl, geen ps.
sh vs bash en BusyBox/Alpine
Probeer eerst:
docker exec -it web sh
Als dat niet werkt:
docker exec -it web /bin/sh
Voor Debian/Ubuntu images is bash vaak aanwezig:
docker exec -it web bash
docker exec -it web /bin/bash
Alpine gebruikt meestal BusyBox sh. Bash installeren kan, maar dat is niet altijd wenselijk in productie.
Debug-tools ontbreken: wat nu?
Je kunt soms tijdelijk tools installeren (alleen als de container package manager heeft en je dat accepteert):
Alpine:
docker exec -it web sh -lc 'apk add --no-cache curl bind-tools iproute2'
Debian/Ubuntu:
docker exec -it web sh -lc 'apt-get update && apt-get install -y curl dnsutils iproute2 procps'
Belangrijk: dit verandert de containerfilesystem (layer) in runtime. Na herstart is het weg (tenzij je commit), maar het kan wel je debug beïnvloeden. In strikte omgevingen wil je dit vermijden en liever een aparte debug-container gebruiken (zie verderop).
docker exec: live inspecteren zonder restart
docker exec start een nieuw proces in de namespace van de draaiende container. Je verandert niets aan de entrypoint, en je hoeft niet te restarten.
Interactief vs niet-interactief
Interactief (terminal):
docker exec -it web sh
Eenmalig commando (scriptbaar):
docker exec web ls -la /app
docker exec web cat /etc/os-release
Een shell met “login command” (-lc) zodat je PATH/aliases goed zitten:
docker exec -it web sh -lc 'echo $PATH; whoami; pwd'
Werken als root of als app-user
Veel containers draaien als niet-root. Soms heb je root nodig om /proc of netwerk te inspecteren.
Check huidige user:
docker exec web id
docker exec web whoami
Als je root nodig hebt:
docker exec -u 0 -it web sh
Of juist als de app-user om permissieproblemen te reproduceren:
docker exec -u 1000 -it web sh
Waarom dit belangrijk is: “werkt op mijn machine” issues zijn vaak permissies: bestanden die root-owned zijn, volumes met verkeerde UID/GID, of een app die niet in /tmp kan schrijven.
Omgevingsvariabelen en configuratie
Omgevingsvariabelen in de container:
docker exec web env | sort
Specifieke variabele:
docker exec web sh -lc 'echo "$DATABASE_URL"'
Configbestanden inspecteren:
docker exec web ls -la /etc
docker exec web cat /app/config.yaml
Let op secrets: wees voorzichtig met het kopiëren/plakken van output in tickets.
Proces- en resource-debugging in een draaiende container
Processen, PID 1 en signalen
Bekijk processen (als ps aanwezig is):
docker exec web ps aux
In minimal images ontbreekt ps. Soms kun je /proc gebruiken:
docker exec web sh -lc 'ls -1 /proc | head'
docker exec web sh -lc 'cat /proc/1/cmdline | tr "\0" " "'
PID 1 in een container is speciaal: het ontvangt signalen anders en moet zombies reapen. Als je app PID 1 is en geen init gebruikt, kun je rare shutdowns krijgen.
Stuur een signaal (bijv. reload of graceful shutdown):
docker kill -s HUP web
docker kill -s TERM web
Als je app niet netjes stopt, kan dat wijzen op signal handling issues.
CPU/RAM, OOM en limits
Snelle live metrics:
docker stats web
Eenmalige snapshot:
docker stats --no-stream web
Check of de container OOM-killed is:
docker inspect -f '{{.State.OOMKilled}}' web
docker inspect -f '{{.State.ExitCode}}' web
docker inspect -f '{{.State.Error}}' web
Geheugenlimieten:
docker inspect -f '{{.HostConfig.Memory}}' web
docker inspect -f '{{.HostConfig.MemorySwap}}' web
Interpretatie:
OOMKilled=truebetekent dat de Linux kernel het proces heeft afgeschoten wegens geheugen.- Een exitcode
137wijst vaak op SIGKILL (kan OOM zijn, of handmatig kill).
Als je host toegang hebt, kijk naar kernel logs:
dmesg -T | grep -i oom | tail -n 50
Netwerk-debugging: DNS, poorten, routes en connectiviteit
Netwerkissues zijn extreem common: verkeerde DNS, service niet bereikbaar, poort niet exposed, firewall, verkeerde localhost aannames.
Poorten en port-mapping
Welke poorten expose je naar de host?
docker port web
docker ps --format 'table {{.Names}}\t{{.Ports}}'
Test vanaf de host:
curl -v http://localhost:8080/
Let op: localhost in de container is de container zelf, niet de host.
DNS in de container
Check resolv.conf:
docker exec web cat /etc/resolv.conf
Test naamresolutie (als getent bestaat):
docker exec web getent hosts example.com
Als nslookup/dig ontbreekt, installeer tijdelijk (Alpine: bind-tools) of gebruik een debug-container.
IP en routes
Als ip aanwezig is:
docker exec web ip addr
docker exec web ip route
Oudere tool:
docker exec web ifconfig
docker exec web netstat -rn
Connectiviteit naar dependencies
Test TCP connectie (als nc aanwezig is):
docker exec web nc -vz db 5432
Met curl (HTTP):
docker exec web curl -v http://api:8000/health
Als je curl niet hebt, soms wget:
docker exec web wget -S -O- http://api:8000/health
Veelgemaakte fout: een app probeert localhost:5432 te bereiken voor Postgres, maar Postgres draait in een andere container. Gebruik dan de servicenaam op het Docker network (bijv. db:5432).
Docker networks en service discovery
Bekijk netwerken:
docker network ls
docker network inspect bridge
Welke netwerken gebruikt je container?
docker inspect -f '{{json .NetworkSettings.Networks}}' web | jq
Container verbinden met een extra netwerk (handig voor debugging):
docker network connect mynet web
Filesystem en volumes: wat staat waar, en waarom is het “weg”?
Containers hebben een ephemeral filesystem. Data die je wilt bewaren hoort in volumes/binds.
Mounts en volumes bekijken
docker inspect -f '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' web
In de container:
docker exec web df -h
docker exec web ls -la /data
“Mijn bestand is weg na restart”
Dat is meestal omdat:
- je schreef naar een pad dat niet gemount is
- je mount overschrijft een directory uit de image
- je app schrijft naar een ander pad dan je dacht
Zo check je of een mount een directory “verbergt”:
- Kijk in de image (zonder container) met
docker run:
docker run --rm -it myimage:tag sh -lc 'ls -la /app'
- Kijk in de draaiende container:
docker exec web ls -la /app
Als /app in de container ineens andere inhoud heeft, dan mount je waarschijnlijk iets op /app.
Bestanden kopiëren voor analyse
Kopieer uit de container:
docker cp web:/app/logs/app.log ./app.log
Kopieer naar de container (bijv. een debugscript):
docker cp ./debug.sh web:/tmp/debug.sh
docker exec web sh /tmp/debug.sh
Geavanceerd: debuggen zonder tools via een “debug sidecar”
In productie-images ontbreken vaak tools (bewust). Je wilt dan debuggen zonder de container te wijzigen. Een krachtige aanpak is een tweede container met tools, op hetzelfde netwerk (en soms met dezelfde PID/IPC namespaces).
Debug-container op hetzelfde netwerk
Stel je app draait op network mynet. Start een tijdelijke toolbox:
docker run --rm -it --network mynet nicolaka/netshoot sh
netshoot bevat o.a. curl, dig, tcpdump, ip, ss.
Test DNS en connectiviteit:
dig api
curl -v http://web:8080/health
nc -vz db 5432
Debuggen “in” de netwerk-namespace van de container
Je kunt een container starten met --network container:<naam> zodat hij exact dezelfde netwerkstack gebruikt:
docker run --rm -it --network container:web nicolaka/netshoot sh
Nu kun je zien wat de app-container ziet, zonder daar tools te installeren:
ip addr
ip route
ss -lntp
curl -v http://127.0.0.1:8080/
Dit is extreem nuttig bij problemen met “luistert de app wel op 0.0.0.0 of alleen op 127.0.0.1?”.
PID namespace delen (processen inspecteren)
Soms wil je strace, lsof of procesdetails, maar die ontbreken. Je kunt PID namespace delen:
docker run --rm -it --pid container:web --network container:web nicolaka/netshoot sh
Dan kun je (afhankelijk van permissies) processen zien die in de andere container draaien:
ps aux
Let op: voor diepere inspectie kan extra capability nodig zijn (--cap-add SYS_PTRACE) maar dat is security-gevoelig.
Veelvoorkomende scenario’s en snelle checklists
Hieronder een set scenario’s met een praktische aanpak.
Scenario 1: Container restart continu (crash loop)
- Status checken:
docker ps -a --filter name=web
- Logs van laatste en vorige run:
docker logs --tail 200 web
docker logs --previous --tail 200 web
- Exitcode/OOM:
docker inspect -f 'OOM={{.State.OOMKilled}} Exit={{.State.ExitCode}} Err={{.State.Error}}' web
- Vaak voorkomende oorzaken:
- missing env var / config file
- database unreachable
- migratie faalt
- verkeerde command/entrypoint
Scenario 2: App “werkt” in container maar niet vanaf host
- Port mapping:
docker port web
docker ps --format 'table {{.Names}}\t{{.Ports}}'
- Luistert de app op 0.0.0.0?
In container (als ss beschikbaar is):
docker exec web ss -lntp
Zonder tools: gebruik debug-container met --network container:web:
docker run --rm -it --network container:web nicolaka/netshoot sh -lc 'ss -lntp; curl -v http://127.0.0.1:8080/'
Als de app alleen op 127.0.0.1 luistert, dan kan de port-forwarding naar buiten falen. Configureer app om op 0.0.0.0 te binden.
Scenario 3: Database connectie faalt (DNS/timeouts)
- Check env var:
docker exec web sh -lc 'echo "$DATABASE_URL"'
- DNS:
docker exec web cat /etc/resolv.conf
- Test connectie (met debug-container):
docker run --rm -it --network container:web nicolaka/netshoot sh -lc 'dig db; nc -vz db 5432'
- Check of containers op hetzelfde network zitten:
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' web
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' db
Scenario 4: Permissions issues op volumes
- Bekijk mounts:
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}} ({{.Type}}){{"\n"}}{{end}}' web
- In container: eigenaar en permissies:
docker exec web ls -la /data
docker exec web id
- Test write:
docker exec web sh -lc 'touch /data/testfile && ls -la /data/testfile'
Als dit faalt: fix UID/GID op host of draai container met juiste user (--user) of pas volume permissies aan.
Scenario 5: Healthcheck faalt maar app lijkt oké
- Inspect health:
docker inspect -f '{{json .State.Health}}' web | jq
- Bekijk healthcheck command:
docker inspect -f '{{json .Config.Healthcheck}}' web | jq
- Run healthcheck handmatig in container:
docker exec web sh -lc 'wget -qO- http://127.0.0.1:8080/health || echo FAIL'
Vaak is het endpoint anders, of luistert de app op een andere poort, of DNS/localhost aannames kloppen niet.
Praktische tips: werkmethodiek en discipline
1) Werk van buiten naar binnen
docker ps→ status/portsdocker logs→ errors/tijdlijndocker inspect→ config, mounts, networksdocker exec/debug-container → live tests
2) Minimaliseer veranderingen
Als je in productie debugt: installeer liever geen packages in de container. Gebruik een debug-container (netshoot) of reproduceer in staging.
3) Leg observaties vast
Kopieer relevante logs met timestamps:
docker logs --since 30m --timestamps web > web-logs.txt
En noteer:
- image tag/digest
- container ID
- exacte env vars (zonder secrets)
- exacte foutmelding + tijd
Referentie: commandoset per categorie
Identificatie & inspectie
docker ps
docker ps -a
docker inspect web
docker events --filter container=web
Logs
docker logs web
docker logs -f --timestamps web
docker logs --since 10m --tail 200 web
docker logs --previous web
Exec & shell
docker exec -it web sh
docker exec -u 0 -it web sh
docker exec web env | sort
docker exec web cat /etc/resolv.conf
Netwerk
docker port web
docker network ls
docker network inspect mynet
docker run --rm -it --network container:web nicolaka/netshoot sh
Resources
docker stats web
docker inspect -f '{{.State.OOMKilled}}' web
Afsluiting
Debuggen van draaiende Docker-containers draait om drie pijlers:
- Logs geven je de tijdlijn en de eerste aanwijzingen.
- Shell toegang geeft je directe observatie van bestanden, config en runtime gedrag.
docker execen debug-containers laten je live testen zonder restarts of image-wijzigingen.
Als je wilt, kan ik op basis van jouw situatie (containernaam, docker ps output, relevante logs, en hoe je container gestart is) een gerichte debug-route uitschrijven met de exacte commando’s in de juiste volgorde.