DevTools Hub

Search tools

Search for a developer tool

How Docker DNS Resolution Works

Part of the Docker Toolkit

A container connecting to db instead of a hardcoded IP looks like magic the first time it works and like a mystery the first time it doesn't. It's neither — it's one specific, documented service: Docker's embedded DNS server, running inside every user-defined network at the fixed address 127.0.0.11.

What actually runs, and where

Every container attached to a user-defined network — anything created with docker network create, or the network Compose creates for you — gets 127.0.0.11 written into its own /etc/resolv.conf as its DNS server. That's not one shared daemon; it's a per-network embedded resolver that knows the name-to-IP mapping for every container attached to that specific network, and only that network. A container on the default bridge — the network literally named bridge — gets none of this: it inherits /etc/resolv.conf straight from the host, with no embedded server in the path at all, which is the entire reason name resolution doesn't work there. The network-membership side of this — which containers are even allowed to reach which — is covered in Understanding Docker Networks.

bridge (default)
resolv.conf inherited from host
appno DNS entry
dbno DNS entry
app-net (user-defined)
resolv.conf → 127.0.0.11
app
db
embedded DNS127.0.0.11
Resolves container names, Compose service names, and network aliases.

What it resolves — and what it forwards elsewhere

The embedded server only knows about names Docker itself assigned: a container's name, a Compose service name, or an explicit network alias. A lookup for anything else — a real domain like api.stripe.com — doesn't fail; the embedded server forwards it to whatever DNS servers the host itself is configured to use, the same resolvers your host machine would use outside a container. From inside a container, both kinds of lookup go through the same 127.0.0.11 address — it's the server deciding whether to answer from its own container-name table or hand the query off, not something the container has to know about or configure.

Compose: the service name is the hostname, by default

docker compose up creates a project-scoped network — named <project>_default unless you declare networks explicitly — and attaches every service to it. Each service's own name becomes resolvable immediately, no configuration needed: a web service reaches a db service at the hostname db. An alias adds an additional resolvable name for a service, scoped to one network:

services:
  db:
    image: postgres:16
    networks:
      back-tier:
        aliases:
          - database

networks:
  back-tier:

Other containers on back-tier can now reach this service as either db or database. It's the modern replacement for the legacy links: option, which did the same aliasing job but implied a connectivity restriction it never actually enforced — reachability was always determined by shared network membership, not by whether a links: entry existed.

Container IPs are dynamic — that's the whole point of using names

A container's IP on a given network can change across a restart or recreation; nothing about it is guaranteed to stay stable. Docker's own guidance is direct about the implication: always reference other services by name, never by IP, precisely because the embedded DNS server is what absorbs that instability — a hardcoded IP that worked yesterday is not a reliable assumption after the next docker compose up.

Going the other direction: reaching the host from inside a container

Everything above resolves names between containers. Reaching the host machine itself from inside a container is a separate, special-cased name: host.docker.internal. On Docker Desktop (Mac, Windows, and Linux) it works automatically — the daemon runs inside a lightweight VM, and Desktop wires up this name to resolve to that VM's host-side address for you. On native Docker Engine on Linux, where the daemon runs directly on the host rather than inside a VM, it isn't available by default; since Engine 20.10 you can add it explicitly:

services:
  web:
    image: myapp
    extra_hosts:
      - "host.docker.internal:host-gateway"

host-gateway is a special value Docker replaces at container start with the actual gateway IP (typically 172.17.0.1 on the default bridge), writing the mapping straight into the container's /etc/hosts — a static entry, not a DNS lookup, which is why it works even for containers on the default bridge network with no embedded DNS server available.

Try it yourself

Docker Network Inspector reads real docker network inspect output and flags exactly this — a container relying on the default bridge network for name resolution that was never going to work. Docker Compose Visualizer shows which services actually share a network, which is what determines whether a name is resolvable in the first place. For the driver-level differences behind all of this, see Docker Bridge vs Host vs Overlay Networks, and for more Compose-specific networking traps, Common Docker Compose Networking Errors. All tools run entirely in your browser.

Related tools