← Terug naar tutorials

Logs, Shells en Exec: Praktische technieken om draaiende Docker-containers te debuggen

dockerdebuggingdocker logsdocker execcontainerstroubleshootingdevopslinux

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:

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

  1. Voorbereiding: containers vinden en context opbouwen
  2. Logs: de snelste route naar oorzaak en tijdlijn
  3. Shells: een prompt krijgen in minimale images
  4. docker exec: live inspecteren zonder restart
  5. Proces- en resource-debugging in een draaiende container
  6. Netwerk-debugging: DNS, poorten, routes en connectiviteit
  7. Filesystem en volumes: wat staat waar, en waarom is het “weg”?
  8. Geavanceerd: debuggen zonder tools via een “debug sidecar”
  9. 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:

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:

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:

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:

Zo check je of een mount een directory “verbergt”:

  1. Kijk in de image (zonder container) met docker run:
docker run --rm -it myimage:tag sh -lc 'ls -la /app'
  1. 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)

  1. Status checken:
docker ps -a --filter name=web
  1. Logs van laatste en vorige run:
docker logs --tail 200 web
docker logs --previous --tail 200 web
  1. Exitcode/OOM:
docker inspect -f 'OOM={{.State.OOMKilled}} Exit={{.State.ExitCode}} Err={{.State.Error}}' web
  1. Vaak voorkomende oorzaken:

Scenario 2: App “werkt” in container maar niet vanaf host

  1. Port mapping:
docker port web
docker ps --format 'table {{.Names}}\t{{.Ports}}'
  1. 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)

  1. Check env var:
docker exec web sh -lc 'echo "$DATABASE_URL"'
  1. DNS:
docker exec web cat /etc/resolv.conf
  1. Test connectie (met debug-container):
docker run --rm -it --network container:web nicolaka/netshoot sh -lc 'dig db; nc -vz db 5432'
  1. 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

  1. Bekijk mounts:
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}} ({{.Type}}){{"\n"}}{{end}}' web
  1. In container: eigenaar en permissies:
docker exec web ls -la /data
docker exec web id
  1. 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é

  1. Inspect health:
docker inspect -f '{{json .State.Health}}' web | jq
  1. Bekijk healthcheck command:
docker inspect -f '{{json .Config.Healthcheck}}' web | jq
  1. 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

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:


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:

  1. Logs geven je de tijdlijn en de eerste aanwijzingen.
  2. Shell toegang geeft je directe observatie van bestanden, config en runtime gedrag.
  3. docker exec en 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.