Skip to main content
RunBook Academy

Docker & ContainersXXVI · DNS & Service DiscoveryEmbedded DNS

Embedded DNS — how service name resolution works

Intermediate⏱ ~24 mindocker

What you'll learn

  • Describe what the embedded resolver answers from and what it forwards
  • Explain why resolution is scoped per network, not per host
  • Predict the behaviour when several containers share one name
  • Recognise that the resolver has no health awareness
  • Diagnose a lookup failure by asking the resolver directly

Prerequisites

None — start here.

Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12

Not yet marked complete on this device.

On a user-defined network, Docker runs an embedded DNS server at 127.0.0.11 inside each container’s network namespace. Container names and network aliases resolve to addresses on that network. On the default bridge network there is no embedded resolver and no service discovery at all.

The important thing about the embedded resolver is what it resolves from. It has no zone file and no cache of its own. Every answer comes from the daemon’s live model of which containers are attached to which networks under which names — which is why the answers are always current, and why “current” is not the same as “working”.

The lookup path

sequenceDiagram
  participant App as Application
  participant DNS as Embedded DNS<br/>127.0.0.11
  participant Reg as Daemon network registry
  participant Up as Upstream resolvers
  App->>DNS: A? api
  DNS->>Reg: is "api" a name or alias<br/>on a network this container is on?
  alt known
    Reg-->>DNS: 172.18.0.5
    DNS-->>App: 172.18.0.5
  else not known
    DNS->>Up: forward, servers tried in order
    Up-->>DNS: answer, or NXDOMAIN
    DNS-->>App: same
  end

Two rules govern the first branch, and both are about scope:

  • The registry is consulted only for networks this container is attached to. A container on frontend cannot resolve a container that exists only on backend, even though both are on the same host and the daemon knows about both.
  • A container attached to several networks can resolve names on all of them. It is the union, not an intersection, and it is why a shared “management” network makes previously invisible names resolvable everywhere.

Forwarding, the second branch, is a plain recursive-resolver relationship: the embedded server “queries upstream servers in order and stops after a successful response or an NXDOMAIN response”.

Asking the resolver directly

Read-only / Safequery the embedded server
CONTAINER=app

# What does the resolver itself say?
docker exec "$CONTAINER" nslookup api 127.0.0.11

# The path the application actually takes: /etc/hosts, then NSS, then DNS
docker exec "$CONTAINER" getent hosts api

# If the image is minimal, borrow a toolbox in the same namespace
docker run --rm -it --network "container:$CONTAINER" \
nicolaka/netshoot dig @127.0.0.11 api

The last form is the one to remember. --network container:$CONTAINER puts the debug container in the same network namespace as the target, so 127.0.0.11 is the same resolver, on the same networks, with the same view. Running dig from a different container answers a different question.

Several containers, one name

Scale a service and more than one container carries the same alias. The resolver returns all of the matching addresses, and clients that use only the first get an even-ish spread as the order varies between answers.

Read-only / Safethree replicas, one alias
$ docker compose up -d --scale worker=3
docker compose exec app getent hosts worker
echo '--- again ---'
docker compose exec app getent hosts worker
172.18.0.4        worker
172.18.0.5        worker
172.18.0.6        worker
--- again ---
172.18.0.5        worker
172.18.0.6        worker
172.18.0.4        worker

Illustrative output

Custom DNS servers

Setting dns: on a service replaces the upstream servers the embedded resolver forwards to. It does not replace the embedded resolver itself, and it does not disable service discovery.

services:
  app:
    image: myapp:1.0.0
    networks: [appnet]
    dns:
      - 192.0.2.53
      - 198.51.100.53
    dns_search:
      - example.com
      - internal.example.com

The container’s /etc/resolv.conf still reads nameserver 127.0.0.11. Container names still resolve from the registry. Only the forwarding target changed. An application configured to talk to 8.8.8.8 directly, bypassing resolv.conf entirely, is the case where service names genuinely stop working — and the fix is to make the application use the system resolver, not to change anything in Docker.

Sanity check

  • docker exec "$CONTAINER" cat /etc/resolv.conf shows nameserver 127.0.0.11.
  • getent hosts from inside the container resolves both a peer service name and an external name.
  • For any name backed by more than one container, something with health checks sits in front of it — you can name what that something is.
  • No two services in the project share a name, even across separate networks.

Knowledge check

Knowledge check · 5 questions

  1. Q1. Embedded DNS resolves service names only for containers on:

  2. Q2. Three containers share the alias `worker`. One is running but wedged and answers nothing. What does the embedded resolver return?

  3. Q3. A container is on the `frontend` network. Another container exists on `backend` only. The first cannot resolve the second. What is happening?

  4. Q4. Setting `dns: [192.0.2.53]` on a Compose service changes which of the following? Select all that apply.

  5. Q5. After a container restarts with a new address, peers must wait for a DNS TTL before they resolve it correctly.

Passing score: 75%. Answers are checked in this browser.