Docker Containers Debuggen: Connection Refused & Time-outs Tussen Services
Wanneer services in Docker “ineens” niet meer met elkaar praten, zie je meestal één van deze symptomen:
Connection refused(directe weigering: poort is dicht of proces luistert niet)Connection timed out(geen antwoord: routing/DNS/iptables/overlay/host firewall of service hangt)No route to host(routing/netwerkpad ontbreekt)Temporary failure in name resolution(DNS-probleem)- Intermitterende failures (race conditions bij opstart, healthchecks, resource pressure)
Deze tutorial is een praktische, diepgaande gids om dit systematisch te debuggen — met echte commando’s, zowel in Docker Compose als in losse containers. We focussen op het verkeer tussen containers (service-to-service), maar nemen ook host↔container en extern verkeer mee waar relevant.
1. Begrijp het verschil: refused vs timed out
1.1 Connection refused
Dit betekent: je bereikt de host/het IP, maar er luistert niets op die poort of een firewall/iptables stuurt actief een RST terug.
Veelvoorkomende oorzaken:
- Applicatie luistert alleen op
127.0.0.1i.p.v.0.0.0.0 - Verkeerde poort (containerpoort vs hostpoort door elkaar)
- Service is gecrasht of nog niet klaar met opstarten
- Je probeert via
localhostin de ene container een service in een andere container te bereiken
1.2 Connection timed out
Dit betekent: je krijgt geen antwoord. Het pakket verdwijnt onderweg of wordt gedropt.
Veelvoorkomende oorzaken:
- Verkeerd netwerk (containers zitten niet op hetzelfde Docker network)
- DNS-resolutie wijst naar een verkeerd IP (stale records, meerdere networks)
- Firewall (host firewall, cloud security group, corporate endpoint security)
- Overlay netwerk issues (Swarm/Kubernetes-achtig)
- De target container hangt (CPU/memory pressure), of accept queue is vol
2. Snelle triage-checklist (5 minuten)
Werk altijd van buiten naar binnen: naam → IP → poort → proces → applicatie.
-
Welke endpoint probeer je?
- Hostnaam/service naam?
- IP?
- Poort?
- Protocol (TCP/UDP)?
-
Zit je in de juiste container?
docker psdocker exec -it <container> sh
-
DNS werkt?
getent hosts <service>nslookup <service>(als aanwezig)cat /etc/resolv.conf
-
TCP connect werkt?
nc -vz <host> <port>curl -v http://<host>:<port/health>(HTTP)timeout 3 bash -c '</dev/tcp/<host>/<port>'(als bash)
-
Luistert de target service wel?
ss -lntpofnetstat -lntpin target containerdocker logs <target> --tail 200
-
Netwerkconfig en policies
docker network lsdocker network inspect <network>iptables/nftop host (indien nodig)
3. Veelgemaakte fout: localhost tussen containers
In Docker betekent localhost de container zelf, niet de host en niet een andere container.
Symptoom
App A probeert http://localhost:5432 te bereiken voor Postgres en krijgt Connection refused.
Fix
Gebruik:
- In Compose: de servicenaam, bv.
postgres:5432 - Of containernaam/alias op hetzelfde network
Controleer DNS vanuit app container:
docker exec -it app sh -lc 'getent hosts postgres && nc -vz postgres 5432'
Als getent hosts een IP teruggeeft en nc connecteert, is DNS + routing OK.
4. Docker Compose: service discovery en networks
4.1 Standaard Compose netwerk
Docker Compose maakt standaard één network aan voor je project. Services kunnen elkaar bereiken via servicenaam.
Controleer:
docker compose ps
docker network ls | grep <projectnaam>
docker network inspect <projectnaam>_default
Let op:
- Elke service krijgt een DNS entry (service name).
- Containers moeten op hetzelfde network zitten.
4.2 Containers op meerdere netwerken: “verkeerd IP” valkuil
Een container kan op meerdere networks zitten. Dan kan getent hosts service meerdere IP’s geven of een IP dat niet routeerbaar is vanuit jouw container.
Check vanuit de client container:
docker exec -it app sh -lc 'getent hosts api; ip route; ip addr'
Check de target container:
docker exec -it api sh -lc 'ip addr; ss -lntp'
Als api luistert op 0.0.0.0:8080 maar je probeert api:80, dan is het gewoon de verkeerde poort.
5. Hostpoort vs containerpoort: de klassieke verwarring
In Compose zie je vaak:
ports: "8080:80"betekent: host 8080 → container 80
Binnen het Docker network moet je bijna altijd de containerpoort gebruiken (hier: 80), niet de hostpoort.
Symptoom
Container A probeert http://service:8080 en krijgt Connection refused (want service luistert intern op 80).
Debug
Bekijk port mapping:
docker port service
docker inspect service --format '{{json .NetworkSettings.Ports}}' | jq
Test intern:
docker exec -it app sh -lc 'nc -vz service 80'
Test vanaf host:
curl -v http://localhost:8080/
6. Controleer of het proces luistert (en waarop)
Een service kan “up” zijn als container, maar de app kan gecrasht zijn of nog niet luisteren.
In target container
Gebruik ss (meestal aanwezig in moderne images) of installeer tools tijdelijk.
docker exec -it api sh -lc 'ss -lntp || netstat -lntp'
Voorbeeld output interpretatie:
LISTEN 0 4096 0.0.0.0:8080→ luistert op alle interfaces (goed)LISTEN 0 4096 127.0.0.1:8080→ alleen loopback (slecht voor andere containers)
Als het alleen op 127.0.0.1 luistert, pas je app config aan:
- Node/Express:
app.listen(port, '0.0.0.0') - Python/Flask:
flask run --host=0.0.0.0 - Uvicorn:
--host 0.0.0.0 - Java/Spring:
server.address=0.0.0.0(of default laten, maar check) - Nginx:
listen 80;(default is vaak ok)
7. Debug tools in minimale images (Alpine, distroless)
Veel containers hebben geen curl, nc, dig, ss. Je hebt opties:
7.1 Tijdelijk tools installeren (Alpine)
docker exec -it app sh -lc 'apk add --no-cache curl bind-tools busybox-extras iproute2'
bind-tools→dig,nslookupbusybox-extras→ vaaknciproute2→ss,ip
7.2 Gebruik een “debug container” op hetzelfde network
Dit is vaak de beste aanpak (geen vervuiling van je app image).
Start een tijdelijke container op hetzelfde network:
docker network ls
docker run --rm -it --network <project>_default nicolaka/netshoot bash
In netshoot heb je curl, dig, tcpdump, mtr, nc, etc.
Test DNS en connectiviteit:
dig api
nc -vz api 8080
curl -v http://api:8080/health
8. Logs: kijk naar opstartvolgorde, crashes en bind-adressen
8.1 Container logs
docker logs api --tail 200
docker logs -f api
Zoek naar:
- “Listening on 127.0.0.1”
- “Address already in use”
- “Connection refused” naar dependencies
- stack traces, OOM kills
8.2 Compose events
docker compose events
docker compose logs -f --tail 200
8.3 OOM / resource pressure
Time-outs kunnen komen doordat de target onder druk staat.
docker stats
docker inspect api --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.State.Error}}'
Op Linux host kun je ook dmesg checken (vereist rechten):
dmesg -T | tail -n 200 | grep -i -E 'killed process|oom'
9. DNS-problemen in Docker: resolutie en caching
Docker gebruikt een ingebouwde DNS (meestal 127.0.0.11 in containers).
Check in de client container:
docker exec -it app sh -lc 'cat /etc/resolv.conf'
Je ziet vaak:
nameserver 127.0.0.11options ndots:0
Test resolutie:
docker exec -it app sh -lc 'getent hosts api'
Als resolutie faalt:
- Zit je wel op hetzelfde network?
- Is de service naam correct?
- Is de container “healthy” of überhaupt gestart?
- Heb je
network_mode: hostgebruikt (dan is Docker DNS anders/niet beschikbaar)?
9.1 extra_hosts en hardcoded IP’s
Hardcoded IP’s breken snel, zeker bij recreates.
Zoek in Compose of env vars naar:
API_HOST=172.18.0.5extra_hosts: - "api:172.18.0.5"
Vervang door servicenaam.
10. Netwerk inspectie: IP’s, routes, firewall
10.1 Welke IP’s hebben containers?
docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' $(docker ps -q)
10.2 Inspecteer een network
docker network inspect <project>_default | jq '.[0].Containers'
Je ziet per container:
- IPv4Address
- MacAddress
- Name
10.3 Routes binnen container
docker exec -it app sh -lc 'ip route'
Normaal zie je een default route via de Docker bridge gateway.
11. Connection refused: diepere oorzaken en oplossingen
11.1 Proces luistert niet (crash / verkeerde command)
Controleer entrypoint/cmd:
docker inspect api --format '{{json .Config.Cmd}} {{json .Config.Entrypoint}}' | jq
Check exit status:
docker ps -a --filter name=api
docker inspect api --format '{{.State.Status}} {{.State.ExitCode}} {{.State.FinishedAt}}'
11.2 Verkeerde bind-interface
Zoals eerder: 127.0.0.1 is een veelvoorkomende fout.
11.3 Poort mismatch
Check welke poort de app echt gebruikt (logs + ss -lntp).
11.4 Healthcheck “green” maar app endpoint down
Soms checkt healthcheck iets anders dan je echte endpoint.
Check health:
docker inspect api --format '{{json .State.Health}}' | jq
12. Connection timed out: diepere oorzaken en oplossingen
12.1 Containers niet op hetzelfde network
In Compose: als je custom networks gebruikt, kan een service per ongeluk alleen op een ander network zitten.
Check:
docker inspect app --format '{{json .NetworkSettings.Networks}}' | jq
docker inspect api --format '{{json .NetworkSettings.Networks}}' | jq
Ze moeten een gedeeld network hebben.
12.2 Host firewall / iptables
Op Linux kan ufw of firewalld Docker verkeer beïnvloeden.
Check basisregels (vereist root):
sudo iptables -S
sudo iptables -L -n -v
sudo nft list ruleset
Docker maakt eigen chains (DOCKER, DOCKER-USER). Als DOCKER-USER een DROP heeft, kan verkeer geblokkeerd worden.
12.3 MTU issues (vooral VPN/overlay)
Time-outs kunnen optreden door fragmentatie/PMTUD problemen.
Symptomen:
- Kleine requests werken, grotere payloads hangen
- Alleen via bepaalde netwerken/VPN
Je kunt MTU checken:
docker network inspect <network> | jq '.[0].Options'
ip link show
Test met ping (als ICMP toegestaan is en ping aanwezig):
docker exec -it app sh -lc 'ping -c 3 api || true'
En met “do not fragment” (niet overal beschikbaar):
docker exec -it app sh -lc 'ping -M do -s 1472 -c 2 api || true'
13. TCP-level debug: nc, curl -v, openssl s_client
13.1 nc (netcat)
docker exec -it app sh -lc 'nc -vz api 8080'
- “succeeded” → TCP connect ok
- “refused” → target luistert niet / poort dicht
- “timed out” → netwerkpad/firewall
13.2 curl -v voor HTTP
docker exec -it app sh -lc 'curl -v --max-time 5 http://api:8080/health'
Let op:
- DNS resolve stap
- “Connected to …”
- HTTP status
13.3 TLS debug
Bij HTTPS:
docker exec -it app sh -lc 'openssl s_client -connect api:8443 -servername api -brief'
Handig om te zien of TLS handshake überhaupt start.
14. Packet capture: tcpdump in containers of op host
Als je echt wilt weten of pakketten aankomen, gebruik tcpdump.
14.1 In een debug container (netshoot)
Start netshoot op hetzelfde network:
docker run --rm -it --network <project>_default --cap-add NET_ADMIN nicolaka/netshoot bash
Capture verkeer naar api:8080:
tcpdump -i any -nn host api and tcp port 8080
Doe nu in een andere terminal een request:
docker exec -it app sh -lc 'nc -vz api 8080'
Interpretatie:
- Zie je SYN → SYN/ACK → ACK? Dan is connectie ok.
- Zie je SYN herhaald zonder antwoord? Dan time-out (dropping).
- Zie je SYN → RST? Dan refused.
14.2 Op de host op de docker bridge interface
Zoek je bridge:
ip link show | grep -E 'docker0|br-'
Capture:
sudo tcpdump -i br-<id> -nn tcp port 8080
15. Compose “depends_on” is geen “ready” garantie
depends_on zorgt alleen dat containers gestart worden, niet dat de service klaar is.
Symptoom
App start, probeert direct DB te bereiken → time-outs/refused → app crasht.
Oplossingen
- Voeg retry/backoff toe in app
- Gebruik healthchecks en wachtlogica (bijv.
wait-for-it,dockerize, eigen script)
Voorbeeld: simpele wait met nc in entrypoint (Alpine shell):
#!/bin/sh
set -e
echo "Wachten op postgres:5432..."
until nc -z postgres 5432; do
sleep 1
done
echo "DB bereikbaar, start app"
exec node server.js
Build dit in je image en gebruik als entrypoint.
16. Praktijkcase: API ↔ Postgres Connection refused
Scenario
apicontainer wil naarpostgres:5432- Krijgt
Connection refused
Stappen
- DNS:
docker exec -it api sh -lc 'getent hosts postgres' - TCP connect:
docker exec -it api sh -lc 'nc -vz postgres 5432' - In postgres container: luistert Postgres op
0.0.0.0:5432?docker exec -it postgres sh -lc 'ss -lntp | grep 5432 || true' - Logs:
docker logs postgres --tail 200
Veelvoorkomende fix:
- Postgres is nog aan het initialiseren → voeg retries toe
- Verkeerde poort (soms
5433) - Postgres bind op localhost door config (zeldzamer in standaard images)
17. Praktijkcase: Connection timed out tussen twee services
Scenario
workerprobeerthttp://api:8080- DNS werkt, maar connect time-out
Stappen
- DNS ok?
docker exec -it worker sh -lc 'getent hosts api' - Is
workerop hetzelfde network?docker inspect worker --format '{{json .NetworkSettings.Networks}}' | jq docker inspect api --format '{{json .NetworkSettings.Networks}}' | jq - Test vanaf netshoot:
docker run --rm -it --network <project>_default nicolaka/netshoot bash nc -vz api 8080- Als netshoot wel kan connecten maar worker niet: probleem in worker container (policy, DNS override, /etc/hosts, proxy vars).
- Check proxy env vars (verraderlijk):
Soms stuurtdocker exec -it worker sh -lc 'env | grep -i proxy || true'HTTP_PROXYverkeer naar een proxy die interne Docker hostnames niet kent → time-outs. Oplossing:NO_PROXY=api,postgres,redis,.local,...
18. Debuggen van applicatie-level time-outs (niet netwerk)
Soms is TCP connect ok, maar de request hangt (bijv. de server accepteert wel maar reageert niet).
Herkenning
nc -vzis “succeeded”curl -vconnecteert maar blijft hangen op response
Dan kijk je naar:
- Thread pool uitgeput
- Deadlocks
- DB calls hangen
- Te weinig workers
- CPU throttling / memory swapping
Commando’s:
docker stats
docker exec -it api sh -lc 'ps aux'
docker logs api --tail 200
Bij Java/.NET kun je extra tooling nodig hebben (jstack/dotnet-dump), maar dat valt buiten deze Docker-netwerkfocus.
19. Handige one-liners en patronen
19.1 Snel testcommando vanuit een container
docker exec -it app sh -lc 'for h in api postgres redis; do echo "== $h =="; getent hosts $h || true; done'
19.2 Poorten checken op target
docker exec -it api sh -lc 'ss -lntp'
19.3 Alle container IP’s
docker ps -q | xargs -n1 docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.NetworkID}} {{.IPAddress}} {{end}}'
19.4 Tijdelijke debug container “in” hetzelfde network namespace
Je kunt ook in de netwerknamespace van een container debuggen (Linux):
docker run --rm -it --network container:api nicolaka/netshoot bash
Dan deel je exact dezelfde netwerkstack als api. Handig om te zien wat api zelf ziet.
20. Samenvatting: een betrouwbaar stappenplan
- Gebruik servicenaam, nooit
localhostvoor andere containers. - Check DNS:
getent hosts <service>. - Check TCP:
nc -vz <service> <poort>. - Check listener in target:
ss -lntpen bevestig0.0.0.0bind. - Check poortverwarring: intern containerpoort, extern hostpoort.
- Check networks:
docker network inspecten gedeelde networks. - Check logs: crashes, bind errors, dependency failures.
- Gebruik netshoot voor tooling en
tcpdumpvoor harde bewijsvoering. - Let op proxies (
HTTP_PROXY/NO_PROXY) en resource pressure (docker stats). - Fix opstart-races met retries/healthchecks i.p.v. alleen
depends_on.
Extra: als je jouw setup deelt
Als je een concreet probleem hebt, plak dan (geanonimiseerd) deze outputs; daarmee kun je meestal binnen minuten de oorzaak vinden:
docker compose ps
docker compose logs --tail 200
docker network ls
docker network inspect <project>_default
docker exec -it <client> sh -lc 'getent hosts <target>; nc -vz <target> <port>; ip route; cat /etc/resolv.conf'
docker exec -it <target> sh -lc 'ss -lntp; ps aux'
Dan kunnen we exact bepalen of het een DNS, network attachment, poort/bind, firewall, of app-level probleem is.