Debugging Network & DNS Issues in Containers
Networking failures are the least reproducible class of DevContainer bug: a service that resolves on one engineer’s machine times out on another’s because of a corporate resolver, a stale bridge network, or a port that was never forwarded. This page gives a deterministic diagnostic order — resolver, then bridge network, then port forwarding — so you stop guessing and start confirming. It is the target for the network-debugging references throughout the DevContainer Architecture & Core Tooling pillar.
Prerequisites
- Docker Engine 24+ (or Podman 4+) with the
dig/nslookuptools available in the container - A
devcontainer.jsonusing either a single container or Docker Compose integration - Shell access via
devcontainer execor an attached terminal
Install the diagnostic tools once via a Feature or onCreateCommand:
{
"image": "mcr.microsoft.com/devcontainers/base:bookworm@sha256:2c5a...",
"onCreateCommand": "sudo apt-get update && sudo apt-get install -y --no-install-recommends dnsutils iproute2 curl",
"remoteUser": "vscode"
}
Architecture & Configuration Deep Dive
A container resolves names through /etc/resolv.conf, which Docker populates from the daemon’s DNS configuration. On user-defined bridge networks, Docker runs an embedded DNS server at 127.0.0.11 that resolves service names to container IPs. Three layers can break:
- Resolver —
127.0.0.11is unreachable or forwards to a DNS server your network blocks. - Service discovery — the embedded DNS only resolves names on the same user-defined network; the default bridge does not provide name resolution.
- Port forwarding — the service listens on
127.0.0.1inside the container, soforwardPortsexposes nothing.
Step-by-Step Implementation
-
Confirm the resolver inside the container.
devcontainer exec --workspace-folder . cat /etc/resolv.conf devcontainer exec --workspace-folder . dig +short github.comAn empty answer means the daemon’s DNS forwarder is blocked — set an explicit resolver (below).
-
Pin a known-good DNS server when a corporate network blocks the default forwarder.
{ "image": "mcr.microsoft.com/devcontainers/base:bookworm@sha256:2c5a...", "runArgs": ["--dns=1.1.1.1", "--dns=8.8.8.8"], "remoteUser": "vscode" } -
Verify service-to-service resolution on a Compose network. Service names must resolve to container IPs:
devcontainer exec --workspace-folder . getent hosts db devcontainer exec --workspace-folder . nc -zv db 5432 -
Put services on the same user-defined network so the embedded DNS resolves names:
services: app: build: . networks: [devnet] db: image: postgres:16-alpine networks: [devnet] networks: devnet: driver: bridge -
Fix port forwarding by binding to all interfaces. A process bound to
127.0.0.1is invisible to the host; bind0.0.0.0:{ "forwardPorts": [3000], "portsAttributes": { "3000": { "label": "web", "onAutoForward": "notify" } }, "remoteUser": "vscode" }
Performance & Resource Optimization
- Cache DNS lookups in long-running dev sessions with a local resolver only if your toolchain re-resolves aggressively; otherwise the embedded DNS is fast enough.
- Avoid
network_mode: hostas a “fix” — it removes isolation and hides the real misconfiguration.
Validation & Testing
# Full path: resolve, then connect, then confirm the forwarded port from the host
devcontainer exec --workspace-folder . dig +short db
devcontainer exec --workspace-folder . curl -fsS http://db:5432 || echo "expected: TCP open, HTTP not spoken"
curl -fsS http://localhost:3000/health
Common Pitfalls
| Symptom | Root Cause | Remediation |
|---|---|---|
| Service name won’t resolve | Containers on the default bridge, which has no name resolution | Attach both services to one user-defined network |
dig returns nothing for public hosts | Corporate network blocks the daemon’s DNS forwarder | Set runArgs: ["--dns=..."] to a reachable resolver |
| Forwarded port shows connection refused | Process bound to 127.0.0.1 inside the container | Bind the service to 0.0.0.0 |
| Resolution works, connection hangs | Service not listening yet (race) | Add a health check before dependent hooks run |
| Intermittent failures after rebuild | Stale bridge network with orphaned endpoints | docker network prune and recreate the container |
Conclusion
Debug networking in a fixed order — resolver, then service discovery on a shared user-defined network, then port binding — and the non-reproducible failures collapse into one of a handful of known causes. Never reach for network_mode: host; it masks the misconfiguration you actually need to fix.
FAQ
Why does db resolve on my machine but not a teammate’s?
One of you is on a user-defined network and the other on the default bridge, or a corporate resolver is blocking the daemon forwarder. Compare /etc/resolv.conf and confirm both services share an explicit network.
Is 127.0.0.11 something I configured?
No — it is Docker’s embedded DNS server, injected automatically on user-defined networks. If it’s missing from resolv.conf, your container is on the default bridge.
How do I expose a database to a GUI client on the host?
Add the port to forwardPorts, ensure the service binds 0.0.0.0, and connect to localhost on the host. The forwarded port maps the host loopback to the container.
Related
- DevContainer Architecture & Core Tooling — the parent pillar covering network topology.
- Resolving DNS failures in Compose networks — the focused fix for service-name resolution.
- Docker Compose Integration for Multi-Service Apps — defining the networks these services share.
- devcontainer.json Property Reference — the
forwardPortsandrunArgsproperties used here.