← Terug naar tutorials

Docker Containers Debuggen: Connection Refused & Time-outs Tussen Services

dockerdocker-networkingdebuggingconnection-refusedtimeoutscontainersdevopstroubleshooting

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:

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:

1.2 Connection timed out

Dit betekent: je krijgt geen antwoord. Het pakket verdwijnt onderweg of wordt gedropt.

Veelvoorkomende oorzaken:


2. Snelle triage-checklist (5 minuten)

Werk altijd van buiten naar binnen: naam → IP → poort → proces → applicatie.

  1. Welke endpoint probeer je?

    • Hostnaam/service naam?
    • IP?
    • Poort?
    • Protocol (TCP/UDP)?
  2. Zit je in de juiste container?

    • docker ps
    • docker exec -it <container> sh
  3. DNS werkt?

    • getent hosts <service>
    • nslookup <service> (als aanwezig)
    • cat /etc/resolv.conf
  4. TCP connect werkt?

    • nc -vz <host> <port>
    • curl -v http://<host>:<port/health> (HTTP)
    • timeout 3 bash -c '</dev/tcp/<host>/<port>' (als bash)
  5. Luistert de target service wel?

    • ss -lntp of netstat -lntp in target container
    • docker logs <target> --tail 200
  6. Netwerkconfig en policies

    • docker network ls
    • docker network inspect <network>
    • iptables/nft op 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:

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:

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:

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:

Als het alleen op 127.0.0.1 luistert, pas je app config aan:


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'

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:

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:

Test resolutie:

docker exec -it app sh -lc 'getent hosts api'

Als resolutie faalt:

9.1 extra_hosts en hardcoded IP’s

Hardcoded IP’s breken snel, zeker bij recreates.

Zoek in Compose of env vars naar:

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:

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:

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'

13.2 curl -v voor HTTP

docker exec -it app sh -lc 'curl -v --max-time 5 http://api:8080/health'

Let op:

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:

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

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

Stappen

  1. DNS:
    docker exec -it api sh -lc 'getent hosts postgres'
  2. TCP connect:
    docker exec -it api sh -lc 'nc -vz postgres 5432'
  3. In postgres container: luistert Postgres op 0.0.0.0:5432?
    docker exec -it postgres sh -lc 'ss -lntp | grep 5432 || true'
  4. Logs:
    docker logs postgres --tail 200

Veelvoorkomende fix:


17. Praktijkcase: Connection timed out tussen twee services

Scenario

Stappen

  1. DNS ok?
    docker exec -it worker sh -lc 'getent hosts api'
  2. Is worker op hetzelfde network?
    docker inspect worker --format '{{json .NetworkSettings.Networks}}' | jq
    docker inspect api --format '{{json .NetworkSettings.Networks}}' | jq
  3. 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).
  4. Check proxy env vars (verraderlijk):
    docker exec -it worker sh -lc 'env | grep -i proxy || true'
    Soms stuurt HTTP_PROXY verkeer 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

Dan kijk je naar:

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

  1. Gebruik servicenaam, nooit localhost voor andere containers.
  2. Check DNS: getent hosts <service>.
  3. Check TCP: nc -vz <service> <poort>.
  4. Check listener in target: ss -lntp en bevestig 0.0.0.0 bind.
  5. Check poortverwarring: intern containerpoort, extern hostpoort.
  6. Check networks: docker network inspect en gedeelde networks.
  7. Check logs: crashes, bind errors, dependency failures.
  8. Gebruik netshoot voor tooling en tcpdump voor harde bewijsvoering.
  9. Let op proxies (HTTP_PROXY/NO_PROXY) en resource pressure (docker stats).
  10. 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.