Docker networking is the number one source of “it works on my machine”
failures in homelabs. A container can’t reach the database. DNS resolves
on the host but not inside the container. Port 8080 is already in use.
The error messages are cryptic, and docker inspect spews JSON that’s
hard to parse under pressure.
This guide covers the five most common Docker networking failure modes,
how to diagnose each one with concrete commands, and the permanent fix.
You’ll learn to use netshoot like a network engineer, decode Docker’s
internal DNS resolution, debug bridge and macvlan interfaces, and build
robust multi-service compose files that survive host reboots and IP
reassignments.
The Diagnostic Toolkit — netshoot
Before fixing anything, you need a containerized network toolkit that
runs in the same network namespace as the broken service. The
nicolaka/netshoot image bundles
everything: tcpdump, dig, nslookup, curl, ping, mtr,
iftop, iperf3, nmap and more — zero installation on the host.
Run netshoot in a Container’s Network Namespace
|
|
Once inside, every tool sees the same network interfaces, routing table, and DNS configuration as the container you’re debugging.
|
|
Common CLI Installation Alternatives
If you can’t pull netshoot (air-gapped or constrained homelab), drop these into any container:
|
|
1. DNS Resolution Failures Inside Containers
Symptom: curl http://service-name works on the host but fails
inside a container with Name or service not known. Pinging by IP
works fine.
Root cause: Docker’s embedded DNS resolver (127.0.0.11) isn’t reaching the configured upstream DNS servers, or the container isn’t on a user-defined bridge network.
Diagnosis
|
|
The Problem with the Default Bridge
Containers attached to Docker’s bridge network (the default) use the
host’s /etc/resolv.conf directly through the host’s loopback,
inheriting whatever DNS servers the host uses. User-defined bridge
networks, on the other hand, get Docker’s embedded DNS resolver at
127.0.0.11, which enables container-name resolution.
Fix — always use a user-defined network:
|
|
If you’re using docker run instead:
|
|
DNS Timeout on User-Defined Networks
If DNS still fails on user-defined networks:
|
|
System-wide fix — configure Docker daemon DNS:
|
|
Save this to /etc/docker/daemon.json and restart:
|
|
2. Port Already in Use
Symptom: docker: Error response from daemon: driver failed programming external connectivity on endpoint <name>: Bind for 0.0.0.0:8080 failed: port is already allocated.
Diagnosis
|
|
Common Causes in Homelabs
- Another container on the same host claims the port. Two services can’t both bind to host port 8080. Move one to a different port:
|
|
- A previous container didn’t release the port. Docker does release ports on container stop, but a stopped container still reserves its IP. Restart or remove it:
|
|
- Traefik/NGINX reverse proxy is listening there. Your reverse proxy binds to 80/443/8080 on the host. Your service shouldn’t also expose those ports — expose an unprivileged port and let the proxy route traffic:
|
|
- systemd-resolved or dnsmasq on port 53. Docker maps container DNS to 127.0.0.11, but if you’re running a local DNS service on the host (Pi-hole, AdGuard Home, dnsmasq), they compete for port 53:
|
|
3. Cross-Container and Cross-Host Connectivity
Symptom: Containers on the same Docker network can’t communicate, or containers on different hosts can’t reach each other.
Same Host, Same User-Defined Network
Containers on the same user-defined bridge can reach each other by
container name, but not by localhost. The container’s idea of
localhost is its own network namespace — not the host’s.
|
|
Fix — use service names, not localhost:
|
|
To reach services on the host from inside a container:
|
|
This adds a /etc/hosts entry that resolves to the Docker gateway
IP (172.x.x.1), which routes to the host’s network stack. Then
inside the container:
|
|
Cross-Host Networking (Multi-Node Docker)
Docker’s default bridge network does not span hosts. Containers on different machines cannot reach each other by container name. Three solutions:
Option A — Expose ports and use host IPs:
|
|
Then from another host: curl http://10.0.20.30:3000.
Option B — Docker Swarm overlay network:
|
|
Then any container attached to homelab-overlay can reach any other
container on that network across hosts — by container name.
Option C — Tailscale or WireGuard mesh:
For a homelab without Docker Swarm, route over Tailscale/WireGuard:
|
|
This requires configuring Tailscale on each Docker host and attaching containers to the Tailscale interface.
4. Bridge and Macvlan Networking Issues
Bridge Network — Container Cannot Reach External Network
Symptom: Containers can talk to each other but cannot reach the
internet. ping 1.1.1.1 hangs.
Diagnosis:
|
|
Fix — enable IP forwarding and check iptables:
|
|
If you use UFW, it overrides Docker’s iptables rules. See the Docker UFW fix for the permanent solution:
|
|
Macvlan — Container Receives DHCP but Can’t Reach Host
Symptom: Macvlan containers get IPs from your DHCP server and can reach external hosts, but cannot reach services running on the Docker host itself.
Root cause: Macvlan bypasses the host’s network stack. The host cannot communicate with macvlan containers unless you create a subinterface.
Diagnosis:
|
|
Fixes:
Option 1 — Use IPvlan L2 instead (lighter, no MAC address per container):
|
|
Option 2 — Add a macvlan subinterface on the host so it can communicate with macvlan containers:
|
|
Option 3 — Route through an external gateway (MikroTik/OPNsense) if you need macvlan isolation:
# On the MikroTik router, add a static route
# /ip route add dst-address=10.0.20.50/32 gateway=10.0.20.1
5. Docker Compose — Networking Configuration Gotchas
Containers Created Before the Network Exists
Symptom: docker compose up fails with network not found.
Fix — Docker Compose v2 creates networks automatically. If you’re manually pre-creating networks, stop:
|
|
If you must share a network across multiple compose files, create it
once with external: true:
|
|
Then create it once:
|
|
Dependency Start Order — Not Network, But Timing
Symptom: Service A (database) takes 30 seconds to initialize. Service B starts immediately and fails because the database isn’t ready — even though the network works.
Fix — add healthchecks and depends_on with condition:
|
|
Docker Compose v2.20+ waits for the depends_on condition before
starting dependent services. This is a timing problem, not a
networking problem — but the error looks exactly like one.
Depends_on Without Condition Is Not Enough
|
|
6. Diagnostic Reference — Quick Command Reference
|
|
Troubleshooting Flowchart
┌──────────────────────────┐
│ Container can't connect │
└──────────┬───────────────┘
▼
┌──────────────────────────┐
│ Is the container running?│
└──────────┬───────────────┘
├── No → docker start <name> or docker logs <name>
│
▼ Yes
┌──────────────────────────┐
│ Can it reach the gateway?│
└──────────┬───────────────┘
├── No → Check iptables, IP forwarding, bridge interface
│ └─ sysctl net.ipv4.ip_forward
│ └─ iptables -t nat -L | grep MASQUERADE
│
▼ Yes
┌──────────────────────────┐
│ Can it resolve DNS? │
└──────────┬───────────────┘
├── No → Is it on a user-defined network?
│ ├── No → Create one, move container
│ └── Yes → Check Docker DNS (127.0.0.11)
│ └── Override with --dns
│
▼ Yes
┌──────────────────────────┐
│ Can it reach the target │
│ service by IP? │
└──────────┬───────────────┘
├── No → Firewall between containers?
│ └── Check docker-proxy, iptables DOCKER-USER
│ └── Check container's exposed ports
│
▼ Yes
┌──────────────────────────┐
│ Can it reach by name? │
└──────────┬───────────────┘
├── No → Same user-defined network?
│ ├── No → Attach to same network
│ └── Yes → Check docker DNS resolution
│ └── docker compose restart
│
▼ Yes
The network works. Check the application logs.
Summary
Docker networking failures are frustrating but follow predictable patterns. When a container can’t connect:
- Run netshoot in the same namespace —
docker run -it --rm --net container:<name> nicolaka/netshoot - Check the network type — user-defined bridge or default?
Use
docker inspect <name>to confirm. - Verify DNS resolution —
nslookupinside the container. 127.0.0.11 means Docker’s resolver is active. Anything else means it inherited the host’s network. - Check port conflicts —
ss -tlnp | grep <port>on the host. One port, one process. - Test by IP first — if IP works but name doesn’t, it’s DNS. If neither works, it’s routing, firewalls, or the network driver.
- Use user-defined networks for all compose files — default bridge lacks DNS-based service discovery.
- Add healthchecks with depends_on conditions — many “networking” errors are actually race conditions at startup.
- Separate reverse proxy ports — let Traefik/NGINX bind to 80/443. Your services expose unprivileged ports internally.
The diagnostic skill that separates experienced homelab operators from beginners isn’t knowing all the answers — it’s knowing the right three commands to run next.