Docker path · Running containers · Topic 9 of 12

Networking: bridges, ports and DNS

Not started

Every container gets its own network namespace: its own interfaces, IP address, routing table, and its own localhost. By default Docker plugs it into a virtual bridge on the host with a veth pair, gives it a private address, and translates its outgoing traffic to the host's address (NAT). Nothing can connect in from outside until you publish a port, and containers find each other by name only on networks you create.

Why it matters: 'Connection refused', 'bad address' and ports that are open to the whole internet all come from the same few rules about bridges, publishing and Docker's DNS.

  1. 1Its own network namespace

    Each container has its own interfaces, IP address, routing table and localhost, connected to a virtual bridge on the host.

  2. 2Publishing opens a door

    -p 8080:3000 forwards a host port to the container. Without an address it listens on all host interfaces, not just localhost.

  3. 3User-defined networks have DNS

    Containers on a network you create resolve each other by name through Docker's embedded DNS server; on the default bridge they do not.

  4. 4EXPOSE publishes nothing

    EXPOSE in a Dockerfile only documents a port; only -p (or Compose ports) makes it reachable from the host.

1

How it works

The explanation, with examples you can run.

Containers talking to containers

docker network create appnetdocker run -d --name api --network appnet hello:1 docker run --rm --network appnet alpine:3.22 wget -qO- http://api:3000/# hello from 46eeb327545b docker run --rm alpine:3.22 wget -qO- -T 2 http://api:3000/      # default bridge# wget: bad address 'api:3000' docker run --rm --network appnet alpine:3.22 cat /etc/resolv.conf# nameserver 127.0.0.11

On a user-defined bridge, Docker runs an embedded DNS server (reached at 127.0.0.11 inside the container) that answers with the IPs of containers on the same network, by container name or network alias. The default bridge network has no such DNS. Compose creates a network per project for you, which is why services there can use each other's names.

Note that the container talks to api:3000, the port the app listens on inside its own namespace. Publishing is not needed for container-to-container traffic.

Publishing ports to the host

docker run -d -p 8080:3000 hello:1             # all host interfaces, port 8080 -> container 3000docker run -d -p 127.0.0.1:8080:3000 hello:1   # only the host itself

Without an address, a published port listens on every interface of the host, so a database published on a cloud VM is open to anyone who can reach that VM. On Linux, Docker inserts its own firewall rules for published ports, and Docker's documentation warns that they are incompatible with ufw: traffic to published ports can bypass ufw's rules. Bind internal services to 127.0.0.1, or do not publish them at all and reach them over a Docker network.

A related hardening arrived in Engine 28.0: older releases let hosts on the same LAN segment reach ports published only to localhost, and even reach unpublished container ports directly; 28.0 blocks that by default.

EXPOSE 3000 in a Dockerfile publishes nothing. It records the intended port as metadata, which docker run -P (publish all exposed ports on random host ports) and humans read.

Other network modes

  • --network host (Linux): the container shares the host's network namespace; no isolation and no port mapping.
  • --network none: only a loopback interface, for jobs that must not talk to anything.
  • macvlan / ipvlan: give containers addresses on the physical network.
  • overlay: spans several hosts (Swarm); Kubernetes uses its own CNI plugins instead.

When something cannot connect, check in order: is it on the same network, does the name resolve (getent hosts api), is the app listening on 0.0.0.0 rather than 127.0.0.1 inside its container, and, from the host, is the port published on the address you are using.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Two containers run on the default bridge. One calls http://api:3000 and gets 'bad address'. Why?
  2. 2.On a cloud VM you run docker run -p 5432:5432 postgres. Who can connect?
  3. 3.Inside a container, what does localhost refer to?

0 of 3 right so far. Get all three to finish this topic.