DNS, discovery & publishing ports
How containers find and expose each other.
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.
$ docker network create appnet$ docker run -d --name db --network appnet postgres:16$ docker run --rm --network appnet nicolaka/netshoot nslookup dbServer: 127.0.0.11Name: dbAddress: 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.
$ 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.
$ docker run -d -p 8080:80 -p 127.0.0.1:9090:9090 -p 53:53/udp --name web myimg$ docker port web80/tcp -> 0.0.0.0:80809090/tcp -> 127.0.0.1:9090 # host-only53/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.
# 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