DNS, discovery & publishing ports

How containers find and expose each other.

Intermediate12 min · lesson 12 of 30

On a user-defined network Docker runs an embedded DNS server at 127.0.0.11 inside each container, resolving container names (and network aliases) to their current IPs. That is service discovery: a container connects to “db” by name, Docker resolves it, and — crucially — the resolution tracks the container even if it is recreated with a new IP. It is why you never hardcode container IPs.

terminal
$ docker network create appnet
$ docker run -d --name db --network appnet postgres:16
$ docker run --rm --network appnet nicolaka/netshoot nslookup db
Server: 127.0.0.11
Name: db
Address: 172.19.0.2

Scenario: stable name across restarts and aliases

A database container is destroyed and recreated during a deploy and comes back with a different IP — yet the app keeps working, because it connects by name and DNS now returns the new address. When you need a service reachable under an extra name (say a legacy hostname), attach a network alias, and the embedded DNS resolves that too.

terminal
$ docker run -d --name db --network appnet --network-alias postgres --network-alias db.internal postgres:16
$ docker run --rm --network appnet nicolaka/netshoot sh -c 'nslookup postgres; nslookup db.internal'
# all three names -> the same container, and they follow it across restarts

Publishing ports to the host

Publishing exposes a container port on the host via NAT rules. -p host:container maps a chosen port; -P publishes every EXPOSE-d port to random high ports; you can pin the host interface and choose the protocol. docker port shows the live mapping. Reserve publishing for what the outside world must reach — internal traffic between containers on a shared network needs no -p at all.

terminal
$ docker run -d -p 8080:80 -p 127.0.0.1:9090:9090 -p 53:53/udp --name web myimg
$ docker port web
80/tcp -> 0.0.0.0:8080
9090/tcp -> 127.0.0.1:9090 # host-only
53/udp -> 0.0.0.0:53

Scenario: expose a debug port to yourself, not the internet

You want a metrics or debug endpoint reachable for local troubleshooting on a cloud host, but not from the public internet. Bind the published port to 127.0.0.1 so only the host itself can reach it; a bare -p binds 0.0.0.0 — every interface, including the public one — which on a cloud box quietly exposes your “local” port to the world.

terminal
# host-only: reachable via localhost on the box, not from outside
$ docker run -d -p 127.0.0.1:6060:6060 --name debug myapp:1.0
# vs the risky default that listens on every interface:
# docker run -d -p 6060:6060 ... -> 0.0.0.0:6060, public on a cloud host
-p defaults to 0.0.0.0 — all interfaces
A plain -p 8080:80 binds every interface on the host, so a port you think of as “local” is reachable from anywhere that can route to the machine — a common way test endpoints end up exposed on cloud VMs. Bind to 127.0.0.1 for host-only access, or put a firewall/security group in front, and publish only what must be public.