Embedded DNS and Service Discovery
On a user-defined network, every container reaches Docker's embedded DNS server at 127.0.0.11, and it resolves container names to their current IP addresses for you. This is why proxy can point upstream at http://web:8000 and web can connect to db:5432 without anyone knowing — or hardcoding — an IP. The name is resolved at request time to whatever address the container currently holds.
The resolver is the payoff of using a user-defined bridge instead of the default one. It works only there: the default bridge has no such server, so a name lookup on it simply fails. Service discovery, in Docker terms, is this one mechanism — names that resolve to live addresses.
db127.0.0.11The Resolver at 127.0.0.11
For each container on a user-defined network, Docker exposes its embedded DNS server at the loopback address 127.0.0.11 and points that container's /etc/resolv.conf at it — there is no separate resolver process running inside the container, just that address wired through to Docker's DNS. A lookup for a container name is answered by this resolver; anything it does not own — a public hostname like api.stripe.com — is forwarded to the host's upstream DNS. The container never knows the difference: it just resolves names, and the right ones come back as container addresses.
Names That Resolve
By default a container's name — whatever you passed to --name, such as db — resolves to its current IP on the shared network. That is the entire discovery mechanism, and the crucial property is that it is re-evaluated on every lookup. When db restarts and draws a new address from the bridge subnet, the next lookup of db returns the new address. Nothing downstream has to change, because nothing downstream ever held the old address.
Network Aliases
A container can answer to more than its name. --network-alias (and Compose's aliases) gives a container additional names on a network. Two uses come up constantly: several containers can share one alias, so a lookup returns multiple addresses for crude round-robin; or a single container can be reachable under a service name distinct from its container name, which is how you decouple "what clients call it" from "what the container is named."
Why Names Beat IPs
Container IPs are assigned from the bridge's subnet at start time and change across restarts — that is not a bug, it is how the bridge allocates addresses. Resolving db to its current address on every lookup means web's database URL is written once as postgres://user:pass@db:5432/driftwood and never edited again. That is the whole point of service discovery: the connection string is a name, and the name is always current.
Driftwood's Wiring
Driftwood's wiring is all names, no addresses. proxy's nginx config sends requests upstream to web; web's DATABASE_URL points at db:5432. Both are resolved by the embedded DNS on driftwood-net, so the configuration is written once and works regardless of which IPs the containers draw on any given day or machine.
upstream driftwood {
server web:8000;
}
server {
listen 80;
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
}
}
The line proxy_pass http://web:8000 is doing the discovery: nginx asks the resolver at 127.0.0.11 for web, gets its current address, and forwards the request. There is no IP anywhere in the file, so the same config is correct after every restart and on every host that runs driftwood-net.
- Hardcoding a container's IP in config and watching it break after a restart assigns a new one — resolve the name through the embedded DNS instead, which always returns the current address.
- Expecting name resolution to work on the default
bridge—127.0.0.11only answers container names on user-defined networks; on the default bridge the lookup fails outright. - Trying to resolve a container by name from a container that is not on the same network — DNS resolves only members of a shared network, so a name on a network you have not joined returns nothing.
- Assuming DNS round-robin across aliases is real load balancing — it returns multiple A records and the client picks one; it spreads connections crudely with no health checking, which is a job for
proxyor an orchestrator.
- Address other containers by name —
web,db— in every connection string and proxy config, so the embedded DNS resolves the current IP and restarts never break the wiring. - Use
--network-aliasor Compose service aliases when a container needs a stable service name distinct from its container name, or when several containers should answer to one name. - Keep every container that must discover another on the same user-defined network, since DNS resolves only shared-network members.
- Put the database URL and upstream host in environment or config as names, never IPs, so the same config works across restarts and across machines.
db.namespace.svc
Podman aardvark-dns mirrors Docker's per-network resolver
Consul · SkyDNS solved name-to-address discovery before it was built into the runtime
Knowledge Check
Where does Docker's embedded DNS listen, and on which networks does it answer container names?
- At
127.0.0.11inside each container, but only on user-defined networks - At
127.0.0.11, and it works on the default bridge as well as user-defined networks - On the host's port 53, shared by every container on the machine
- At a public Docker DNS endpoint that every container queries over the internet
Why does resolving a container by name survive that container getting a new IP after a restart?
- The name is re-resolved on every lookup, so the next lookup returns the new current address
- Docker guarantees that each container keeps the exact same IP address across every restart forever
- Docker writes the IP into
/etc/hostsonce at start and never needs to update that file - The DNS server pushes a live update to every connected client whenever an IP changes
What does a network alias let you do?
- Give a container additional names on a network, including a name several containers can share
- Resolve the names of containers sitting on networks that this container has not actually joined
- Automatically publish the container's listening ports to the host under that chosen name
- Assign the container a fixed, reserved IP address that never changes on restart
Why is DNS round-robin across aliases not real load balancing?
- It returns multiple addresses and the client picks one, with no health checking of the backends
- It always sends every single connection to the same container, ignoring all the others behind the name
- It only resolves names on the default bridge, where any real load balancing is impossible
- It encrypts each request individually, which is far too slow to balance load effectively
You got correct