skip to content

Embedded DNS & Service Discovery

How containers find each other by name: the embedded resolver at 127.0.0.11, container names and network aliases, and what happens to names it cannot answer. Probed because one alias can front several containers and clients cache the first answer.

part ofDockeroverview, primer and where to startread it →
on this pageshow

questions

4

In Docker, how does one container reach another container by name, and why does that same lookup fail when both containers run on the built-in default `bridge` network?

level: juniorimportance: must knowfreq 68%

answer

  1. nameserver 127.0.0.11
  2. user-defined net = DNS; default bridge = no DNS
  3. name, hostname, alias, compose service
  4. scope is per-network
  5. --link deprecated, stale /etc/hosts

basics

~20 s

On a user-defined network Docker runs an embedded DNS server at 127.0.0.11 that resolves container names and network aliases to container IPs. The built-in default bridge network has no such DNS: there you only have raw IPs or the deprecated --link flag.

solid answer

~50 s

Any container attached to a **user-defined** network gets `nameserver 127.0.0.11` in its `/etc/resolv.conf`. That is Docker's embedded DNS server, reachable only from inside that container's network namespace and answered by the Docker daemon. It resolves other containers on the *same* user-defined network by container name, by the container's hostname, by Compose service name, and by any `--network-alias`. Names it does not know (public domains) it forwards to the host's upstream resolvers. The legacy default `bridge` network - the one you get when you pass no `--network` - deliberately does not provide this. Containers there can only talk by IP, or via the deprecated `--link` flag, which wrote one-way `/etc/hosts` entries at start time and went stale as soon as the target was recreated. The practical rule: **always create a user-defined network** (`docker network create app`, or use Compose, which creates one per project) and address containers by name. Container IPs are pool-assigned and change on recreation; names do not.

code

bash · 7 lines
bash
docker network create app
docker run -d --name db --network app postgres:16
docker run --rm --network app alpine getent hosts db
# 172.18.0.2  db

docker run -d --name db2 postgres:16          # default bridge
docker run --rm alpine getent hosts db2       # no output, exit code 2

go deeper

for a junior

Know that user-defined networks (or Compose) give you container-name resolution and that the default bridge does not, and that you should never hardcode container IPs.

for a middle

Add the mechanism: 127.0.0.11 in /etc/resolv.conf, resolution scoped per network, aliases and Compose service names, and why --link is dead.

for a senior

Bring in operational consequences: names are stable across recreation but client-side DNS caching is not, and network scoping is a deliberate segmentation tool.

for a principal

Frame it as the discovery layer of a deployment: name-based addressing is what makes containers replaceable, and the same contract is what an orchestrator later provides at cluster scale.

## Why names instead of IPs Containers get IPs from Docker's IPAM pool (the default bridge uses roughly `172.17.0.0/16`; each user-defined network gets its own subnet). Those addresses are not stable: recreate a container and it may come back with a different one. Hardcoding IPs in configuration therefore breaks on every redeploy, so Docker provides name-based discovery. ## What a user-defined network gives you When you run `docker network create app` and attach containers with `--network app`, each container's `/etc/resolv.conf` contains a single entry: `nameserver 127.0.0.11`. That is the **embedded DNS server**. It is not a process inside your container image - the address is intercepted inside the container's network namespace and served by the Docker daemon, which knows every container attached to every network. The embedded resolver answers for: - the **container name** (`--name api`) - the container **hostname** (`--hostname`, defaults to the short container ID) - every **network alias** (`--network-alias`, or Compose `networks.<net>.aliases`) - in Compose, the **service name**, which is registered as an alias Crucially the scope is **per network**: a lookup only returns containers that share a network with the caller. Two containers on different user-defined networks cannot resolve each other, which is how you get isolation (a `frontend` network and a `backend` network, with the API attached to both). Anything the resolver does not recognise as a container name is **forwarded** to the DNS servers the daemon derived from the host (or from `--dns` / `daemon.json`), so `api.example.com` still resolves normally. ## Why the default bridge is different The default `bridge` network predates the embedded DNS and is kept for backwards compatibility. Containers on it get connectivity (outbound NAT, and each other's IPs are reachable) but **no DNS-based discovery**. `ping api` fails with a resolution error; `ping 172.17.0.3` works. The historical workaround was `--link`: ``` docker run --name db postgres docker run --link db:db myapp ``` `--link` injected an `/etc/hosts` line into the *linking* container at start time (and shared the target's environment variables). It is **deprecated**, for good reasons: it is one-directional, it is resolved once at start so a restarted `db` with a new IP leaves a stale entry, it forces a start order, and it does not work across hosts. Do not propose it in an interview except as history. ## What this looks like in practice Docker Compose does the right thing by default: it creates a project network and registers each service name, so `http://api:8080` works from any other service in the file with no configuration. If you are running raw `docker run` commands, one `docker network create` up front buys the same behaviour. Two details worth remembering. First, resolution returns the container's **internal** IP on that network, so you connect to the container's real port - not to any published host port. Second, `/etc/hosts` inside the container still only contains the container's own hostname mapping and localhost entries; peer lookups go through DNS, not through a hosts file that Docker keeps rewriting.

  • Why is `--link` deprecated, and what replaced it?
    `--link` injected a static `/etc/hosts` entry into the linking container when it started, plus the target's environment variables. It is one-way, requires a fixed start order, and goes stale the moment the target container is recreated with a new IP. User-defined networks with embedded DNS replaced it: resolution is dynamic, symmetric, and survives recreation.
  • A container is recreated and gets a new IP. Does the name keep working?
    Yes - the embedded DNS is backed by the daemon's live view of the network, so the next lookup returns the new address. The failure mode is on the client side: applications or runtimes that cache DNS results (for example a JVM with an infinite `networkaddress.cache.ttl`, or a long-lived connection pool) keep using the old IP until they re-resolve.
  • Can two containers on different user-defined networks resolve each other by name?
    No. Lookups are scoped to networks the caller is attached to. To let them talk, attach one container to both networks (`docker network connect`) or put them on a shared network. That scoping is the main tool for segmenting, for example, a public-facing tier from a database tier.

A user-defined network is like an office floor with its own internal phone directory: dial a colleague by name. The default bridge is the same floor with no directory - you must know the extension number, and numbers get reassigned.

saying these in an interview costs you the question

  • Claiming Docker keeps every container's name in every container's /etc/hosts - peer names come from DNS, not a hosts file
  • Recommending `--link` as the modern way to connect containers
  • Assuming name resolution works on the default bridge network
  • Thinking you must connect to the published host port to reach a container on the same network - you use its internal port
  • Believing containers on separate user-defined networks can resolve each other by name

context

open as a page

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%

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.

open as a page

You attach three Docker containers to the same network, each with `--network-alias api`. What does a lookup of the name `api` return, and what are the limits of relying on that for load balancing?

level: middleimportance: should knowfreq 36%

basics

~20 s

The lookup returns all three container IPs, with the order shuffled per query, so clients spread naturally. It is DNS round-robin only: no health checking, no connection-level balancing, and client-side caching or long-lived connections can pin traffic to one container.

open as a page

How does Docker decide which upstream DNS servers a container uses, and what would you change if every container must query a corporate internal DNS server instead?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Docker derives upstream resolvers from the host's /etc/resolv.conf, dropping loopback entries and falling back to public resolvers if nothing is left. Override globally with the daemon.json dns/dns-search/dns-opts keys, or per container with --dns, --dns-search, --dns-option, or Compose's dns keys.

open as a page