sehaj/learn
Docker/05 Networking

05.04 · with code · 13 min

Debugging network problems

A repeatable checklist for 'container A can't reach B', the five mistakes behind almost every case, and a toolbox container that works even on distroless images.

The core idea

"It can't connect" has a small number of causes, and you can rule them out in order: does the name resolve, is anything listening, can packets get there, and is the app using the right address? Work from the outside in and stop guessing.

The toolbox

Most images don't ship dig, curl, nc or ss, and distroless ones don't even have a shell. Bring your own: nicolaka/netshoot is an image packed with network tools. Run it on the same network as the thing you're debugging, or inside its network namespace:

toolbox
# On a network, as a neighbour
docker run --rm -it --network notes-backend nicolaka/netshoot

# Inside another container's network: same IP, same localhost, same ports
docker run --rm -it --network container:notes nicolaka/netshoot

The checklist

1. Are they on the same network?

❯terminal
$ docker network inspect notes-frontend -f '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
notes 172.21.0.3/16
notes-proxy 172.21.0.2/16

If the caller isn't listed next to the target, nothing else matters. A container started without --network is on the default bridge and can't resolve anyone:

❯terminal
$ docker run --rm alpine wget -qO- -T 3 http://notes:3000/healthz
wget: bad address 'notes:3000'

2. Does the name resolve, from the caller's side?

❯terminal
$ docker run --rm --network notes-frontend nicolaka/netshoot dig +short notes notes-db
172.21.0.3

notes resolves; notes-db returns nothing because it's only on notes-backend. That's by design here, but it's the answer you'd be looking for.

3. Is anything listening, on the right address?

❯terminal
$ docker run --rm --network container:notes nicolaka/netshoot ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 127.0.0.11:46567 0.0.0.0:*
LISTEN 0 511 *:3000 *:*

*:3000 means the API listens on all interfaces. If you saw 127.0.0.1:3000 instead, other containers could never reach it: the bind-address bug from 03.02 Publishing ports. (127.0.0.11 is Docker's DNS server.)

4. Does the port answer?

❯terminal
$ docker run --rm --network notes-backend nicolaka/netshoot sh -c 'nc -zv -w 2 notes-db 5432; nc -zv -w 2 notes-db 5433'
Connection to notes-db (172.20.0.2) 5432 port [tcp/postgresql] succeeded!
nc: connect to notes-db (172.20.0.2) port 5433 (tcp) failed: Connection refused

"Connection refused" means the host is reachable but nothing listens on that port. A timeout means packets aren't getting there at all: a different network, or a firewall.

5. Is the app actually using that address? Read its config and logs. The most common culprit is in the next section.

The five usual suspects

SymptomCauseFix
bad address / ENOTFOUNDnot on the same user-defined network, or a typodocker network connect, check the name
refused on localhostthe app points at localhost for another containeruse the container's name
refused on the right hostwrong port, often the published port instead of the container portcontainers use the container port
works inside, not from outsideapp bound to 127.0.0.1listen on 0.0.0.0
timeoutnetworks not connected, or a firewallcheck docker network inspect

The localhost one deserves its own example. Inside a container, localhost is that container:

❯terminal
$ docker run --rm --network notes-backend postgres:18 psql postgres://notes:notes@localhost:5432/notes -c 'select 1'
psql: error: connection to server at "localhost" (::1), port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?

The database is running fine, just in a different container. postgres://notes:notes@notes-db:5432/notes works.

Predict first

The database container was started with -p 5433:5432. The API, on the same network, connects to notes-db:5433 and gets 'connection refused'. Why?

Commit to an answer before you read on.

Seeing the request itself

When the connection works but the response is wrong, look at the HTTP exchange from the caller's side:

❯terminal
$ docker exec notes-proxy wget -S -qO- -T 3 http://notes:3000/healthz
HTTP/1.1 200 OK
content-type: application/json
...
{"status":"ok"}

Output trimmed. From netshoot, curl -v gives the same view with more detail. For anything deeper, netshoot also has tcpdump: docker run --rm --network container:notes nicolaka/netshoot tcpdump -i eth0 port 5432 shows every packet the API sends to Postgres.

CheckpointBreak it, then find it

With the step 4 stack running, break it three ways, one at a time, and diagnose each with the checklist before fixing it:

  • Disconnect the API from notes-frontend (docker network disconnect). What does nginx return, and why?
  • Recreate the API with DATABASE_URL pointing at localhost. What do its logs say?
  • Recreate the API pointing at port 5433. What does nc tell you?
STUCK? ▸

If you're stuck, walk the checklist in order and write down the answer to each step. The first "no" is your bug.

Recap

Interview questions

SeniorfreqMust-knowDebugging

Two services on the same Docker network can't reach each other by name. Walk me through debugging it.

Report a mistakeEdit this page