skip to content

Docker's embedded DNS server answers at 127.0.0.11 inside a container. Explain how queries actually reach it, and what it does with a name it does not recognise.

level: middleimportance: should knowfreq 42%

answer

  1. loopback inside the container netns
  2. NAT redirect 53 -> daemon listener
  3. internal records first, then forward upstream
  4. UDP and TCP both redirected
  5. debug from a sidecar on the same network

basics

~20 s

127.0.0.11 is a loopback address inside the container's own network namespace; NAT rules there redirect port 53 traffic to a listener the Docker daemon runs for that namespace. Container names are answered locally; unknown names are forwarded to the daemon's configured upstream resolvers.

solid answer

~60 s

The address lives in the container's **network namespace**, not on the host and not in any process you can see with `ps`. Docker installs NAT rules in that namespace redirecting traffic to `127.0.0.11:53` (UDP and TCP) to an ephemeral port where the daemon-side resolver listens. Because it is namespace-local, no other container can query it, and its answers are automatically scoped to the networks that container is attached to. On a query the resolver first checks its internal record set: container names, hostnames, and network aliases on shared networks. A hit returns the container's IP on that network. A miss is **forwarded** to the upstream nameservers the daemon derived from the host's `/etc/resolv.conf`, or from `--dns` / `daemon.json`, and the reply is proxied back. Two consequences matter operationally: DNS works even when the container has no route to any external resolver as long as only container names are queried, and if the upstream servers are wrong or unreachable, container-name lookups still succeed while public names time out - a very characteristic split failure.

code

bash · 6 lines
bash
docker run --rm -it --network app nicolaka/netshoot \
  dig +short @127.0.0.11 db

# splitting internal vs external resolution
docker run --rm --network app alpine sh -c \
  'getent hosts db; getent hosts example.com'

go deeper

for a junior

It is enough to know 127.0.0.11 is Docker's built-in resolver for container names and that other names are passed through to normal DNS.

for a middle

Explain the namespace-local loopback plus NAT redirect to a daemon-side listener, the internal-records-then-forward order, and that no process in the image serves it.

for a senior

Use the split failure modes as a diagnostic: internal-only working means forwarding is broken; external-only working means the containers do not share a network.

for a principal

Note the design property - a fixed, namespace-scoped resolver address makes images environment-agnostic, and the same idea reappears as cluster DNS in orchestrators.

## Where 127.0.0.11 lives A container has its own **network namespace**: its own interfaces, routing table, loopback and firewall rules. `127.0.0.11` is an address on that namespace's loopback. From the host you cannot query it; from another container you cannot query it. That isolation is the point - each container's resolver view is different, because each is attached to a different set of networks. Nothing inside the container image listens on it. Docker programs rules in the container namespace's NAT table that redirect packets destined for `127.0.0.11:53` (both UDP and TCP) to a high port bound by the daemon for that namespace, with matching translation on the way back so the reply appears to come from `127.0.0.11:53`. The resolver logic itself runs in the daemon's networking layer, which already holds the authoritative map of container to network to IP. ## Resolution order 1. The library resolver in the container reads `/etc/resolv.conf`, sees `nameserver 127.0.0.11`, and sends the query. 2. The embedded resolver checks its **internal records** for the networks this container is on: container names, hostnames, network aliases, Compose service names. 3. On a hit it answers with the target's IP **on the shared network** (if two containers share several networks, the answer is for one of them). 4. On a miss it acts as a **forwarder**: it sends the query to the configured external nameservers and proxies the answer back. That forwarding step is why `curl https://example.com` works from inside a container even though its resolv.conf lists only a loopback address. ## Why the design matters **Namespace-local means no cross-talk.** A container cannot enumerate names on networks it is not attached to, so DNS scoping doubles as an isolation boundary. **Loopback means the address is constant.** Every container, on every host, uses the same `127.0.0.11`, so images need no per-environment DNS configuration. **TCP is supported.** The redirect covers TCP 53, so answers too large for a UDP datagram can fall back to TCP - relevant when an alias resolves to many container IPs. ## Characteristic failure modes - **Container names resolve, public names do not.** The internal record set is served locally; forwarding is broken. Usually the host's resolv.conf pointed at something unreachable from the container namespace, or an egress firewall blocks port 53. - **Public names resolve, container names do not.** Almost always the containers are not on the same user-defined network (or one is on the default bridge). - **Resolution succeeds, connection fails.** DNS is doing its job; the problem is the target port, the app binding to 127.0.0.1 inside its own container, or a firewall. - **Alpine/musl quirks.** musl libc's resolver has historically queried listed nameservers in parallel and handled search domains and TCP fallback differently from glibc, which produces "works on Debian, flaky on Alpine" reports. ## How to inspect it Many minimal images have no `dig` or `nslookup`. `getent hosts <name>` uses the C library resolver and is almost always available; for anything deeper, attach a debug container to the same network (`docker run --rm --network app -it nicolaka/netshoot`) and query `dig @127.0.0.11 <name>` from there. Do not try to `dig @127.0.0.11` from the host - that address means nothing outside a container namespace.

  • Container names resolve fine but every public hostname times out. Where do you look?
    The embedded resolver's forwarding path. Check which upstream servers the daemon handed the container (host `/etc/resolv.conf`, `--dns`, or `daemon.json` `dns`), and whether those servers are reachable from the container's namespace - a host pointing at a local stub resolver, or an egress firewall blocking UDP/TCP 53, produces exactly this split.
  • Can you query 127.0.0.11 from the host to debug a name?
    No. The address only exists inside container network namespaces, served by NAT rules installed there. To test it you must be inside a namespace - either `docker exec` into the container itself, or run a debug image such as netshoot attached to the same network.

saying these in an interview costs you the question

  • Claiming a DNS daemon runs inside the container image
  • Trying to `dig @127.0.0.11` from the host and concluding DNS is broken
  • Assuming the embedded resolver is authoritative for public domains rather than forwarding them
  • Thinking only UDP is handled, so large answers must fail
  • Confusing failure to resolve with failure to connect

context