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/nslookup tools available in the container
  • A devcontainer.json using either a single container or Docker Compose integration
  • Shell access via devcontainer exec or 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:

  1. Resolver127.0.0.11 is unreachable or forwards to a DNS server your network blocks.
  2. Service discovery — the embedded DNS only resolves names on the same user-defined network; the default bridge does not provide name resolution.
  3. Port forwarding — the service listens on 127.0.0.1 inside the container, so forwardPorts exposes nothing.

Step-by-Step Implementation

  1. Confirm the resolver inside the container.

    devcontainer exec --workspace-folder . cat /etc/resolv.conf
    devcontainer exec --workspace-folder . dig +short github.com
    

    An empty answer means the daemon’s DNS forwarder is blocked — set an explicit resolver (below).

  2. 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"
    }
    
  3. 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
    
  4. 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
    
  5. Fix port forwarding by binding to all interfaces. A process bound to 127.0.0.1 is invisible to the host; bind 0.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: host as 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

SymptomRoot CauseRemediation
Service name won’t resolveContainers on the default bridge, which has no name resolutionAttach both services to one user-defined network
dig returns nothing for public hostsCorporate network blocks the daemon’s DNS forwarderSet runArgs: ["--dns=..."] to a reachable resolver
Forwarded port shows connection refusedProcess bound to 127.0.0.1 inside the containerBind the service to 0.0.0.0
Resolution works, connection hangsService not listening yet (race)Add a health check before dependent hooks run
Intermittent failures after rebuildStale bridge network with orphaned endpointsdocker 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.