sehaj/learn
Docker/05 Networking

05.01 · with code · 11 min

How container networking works

What actually connects a container to the world: a private network namespace, a virtual cable, a bridge, and some address translation.

The core idea

Every container has its own network stack: its own network card, its own address, its own list of where to send traffic, and its own ports. In plain words, it behaves like a separate small computer plugged into a network. Docker connects that private network to the outside world with three pieces of ordinary Linux networking:

How a container reaches the network
  1. 01

    veth pair

    A virtual cable. One end is the container's eth0, the other end sits on the host.

  2. 02

    Bridge

    A virtual switch on the host. Every container's cable plugs into it, so they can talk.

  3. 03

    NAT out

    Traffic leaving for the internet is rewritten to come from the host's IP.

  4. 04

    NAT in (-p)

    Published ports rewrite incoming traffic to the container's IP and port.

On Docker Desktop all of this happens inside the Linux VM; the idea is the same.

The networks Docker gives you

❯terminal
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
ed8a6de1e4b5 bridge bridge local
... host host local
... none null local
$ docker network inspect bridge -f '{{range .IPAM.Config}}subnet={{.Subnet}} gateway={{.Gateway}}{{end}}'
subnet=172.17.0.0/16 gateway=172.17.0.1

Output trimmed. Containers you start without --network join bridge, the default bridge, and get an address from 172.17.0.0/16. host and none are special modes, covered in 05.03 Host and none networks.

The view from inside

❯terminal
$ docker run --rm alpine sh -c 'ip -4 addr show eth0 | grep inet; ip route; cat /etc/resolv.conf'
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link src 172.17.0.2
nameserver 192.168.65.7
  • eth0 is the container's end of the virtual cable, with its own IP.
  • default via 172.17.0.1: anything not on the local subnet goes to the bridge's gateway, the host.
  • resolv.conf on the default bridge is copied from the host (here, Docker Desktop's resolver). Remember this; it changes on user-defined networks.

Talking to each other, and out

Containers on the same bridge can reach each other by IP. Outbound traffic is NAT'ed through the host, so the internet just works:

❯terminal
$ docker run -d --name web1 nginx:alpine
$ docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' web1
172.17.0.2
$ docker run --rm alpine wget -qO- http://172.17.0.2 | grep -o '<title>.*</title>'
<title>Welcome to nginx!</title>
$ docker run --rm alpine wget -qO- http://web1
wget: bad address 'web1'
$ docker run --rm alpine wget -qO- http://example.com | grep -o '<title>.*</title>'
<title>Example Domain</title>

By IP it works; by name it doesn't. The default bridge has no DNS for container names. And IPs are handed out fresh every time a container starts, so hard-coding them breaks on the next restart. That's why real setups use user-defined networks (05.02 User-defined bridges and DNS).

Where -p fits in

Publishing -p 8080:80 adds a rule on the host: "traffic arriving on port 8080 → rewrite to this container's IP, port 80". That's the NAT-in step. It's also why containers on the same network never need -p to reach each other: they talk directly over the bridge.

Predict first

Two containers on the default bridge need to talk. The API connects to the database at 172.17.0.3, which works. What goes wrong next week?

Commit to an answer before you read on.
CheckpointMap your container's network

Start two alpine sleep 300 containers. For each, find its IP with docker inspect and from inside with ip addr. Ping one from the other by IP, then try by name. Stop and start one and check whether its IP changed.

STUCK? ▸

No ip command: that's an image without iproute2 or busybox. Use alpine or nicolaka/netshoot for exploring.

Recap

Interview questions

MidfreqGood to knowConcept

Explain how Docker's default bridge networking works.

Report a mistakeEdit this page