Chapter 7: Networking
Topic 39

The Container Network Model

KernelNetworking

Container networking is not a packet engine Docker invented. It is the same Linux kernel primitives from Chapter 1 — namespaces and cgroups — applied to the network stack, plus two pieces of plumbing the daemon wires up for you: a virtual ethernet cable and a software switch. Each container gets its own network namespace, so inside it the only interface is the one Docker created and localhost means the container, not the host.

Docker joins that isolated namespace to the outside world with a veth pair plugged into a Linux bridge, and rewrites the source address on outbound packets with NAT. Once you can draw that picture — a process in its own net namespace, a veth into a bridge, iptables for egress — every later behavior in this chapter is a consequence, not a new rule to memorize.

The Network Namespace

Every container gets its own network namespace — the net namespace from topic 03 — which means its own loopback device, its own interfaces, its own routing table, and its own iptables rules. This is why two containers can both bind port 8000 without colliding: each binds inside its own namespace, and the two namespaces never see each other's sockets. It is also why 127.0.0.1 inside a container is not the host. A process listening on 127.0.0.1 inside web has its own private loopback; proxy, with a separate loopback, cannot reach it at all.

The consequence is sharp and trips up nearly everyone once: binding a service to loopback inside a container makes it reachable only from that same container. To be reachable by a sibling container or by a published port, a server must bind 0.0.0.0 — every interface — which is why most server defaults already do.

The veth Pair

A container's namespace would be sealed off with nothing but loopback unless something connected it to the host. That something is a virtual ethernet pair: a kernel cable with two ends. One end becomes eth0 inside the container's namespace; the other end lands on the host. Anything pushed in one end comes out the other, so the veth is the bridge between the isolated namespace and the host's network. Create a container and a fresh veth pair appears; remove it and the pair is torn down.

The Linux Bridge

The host end of every container's veth plugs into a software switch — a Linux bridge. For the default network that bridge is docker0; a user-defined network gets its own. The bridge forwards frames between everything attached to it, exactly as a physical switch forwards between ports, which is what lets two containers on the same bridge reach each other directly by IP. Containers on different bridges share no switch, so they cannot reach each other at all — the isolation that topic 44 turns into a deliberate tool.

A packet's path out of a container
Inside the namespace
The process sends from eth0, the container end of a veth pair. Its own loopback, routing table, and iptables rules are all private.
On the host
The other veth end is plugged into the bridge; the daemon's iptables masquerade rules rewrite the source to the host's IP on the way to the LAN.

NAT and iptables for Egress

A container's address comes from the bridge's private subnet — something like 172.17.0.2 — and that address is not routable anywhere off the host. So when the container talks to the internet, the daemon's iptables masquerade rules rewrite the source address to the host's own IP on the way out, and rewrite the replies back on the way in. Outbound traffic works because the kernel is doing source NAT, not because the container has a real address on the network. When egress breaks, the answer is in the host's iptables NAT table and routing — iptables -t nat -L — not in any Docker-specific magic.

Why This View Matters

Hold the picture — a process in its own net namespace, joined by a veth to a bridge, with NAT for egress — and the rest of the chapter stops being a list of features. Publishing a port (topic 42) is a DNAT rule the daemon adds to forward a host port into the namespace. The default-versus-user-defined-bridge difference (topic 40) is simply which switch you are plugged into and whether a DNS resolver came along. The host mode (topic 43) is skipping the namespace entirely so there is nothing to NAT across. Every one of those is a variation on the same diagram.

It also tells you how to debug. Because it is all standard Linux, the standard Linux tools apply: ip to inspect interfaces and routes, iptables -t nat -L to read the NAT rules, nsenter to step inside a container's namespace, and docker network inspect to see who shares a bridge and on what subnet. You are not guessing at a black box; you are reading kernel state.

Common Mistakes
  • Assuming localhost inside a container reaches the host or a sibling — it reaches that container's own loopback. A process bound to 127.0.0.1 inside web is unreachable from proxy, which has a separate loopback of its own.
  • Binding an in-container service to 127.0.0.1 and then trying to publish it — the published port forwards to the container's external interface, not its loopback, so the connection is refused. Inside a container, bind 0.0.0.0.
  • Expecting a container's 172.17.x.x address to be reachable from another machine — it is a private address behind the host's NAT, routable only on that host's bridge, never on the LAN.
  • Reasoning about container networking as if Docker had its own packet stack — it is veth pairs, a bridge, and iptables. When egress breaks, the host's NAT rules and routing table hold the answer.
Best Practices
  • Reason about container networking through the four primitives — namespace, veth, bridge, iptables — so you can debug with ip, iptables -t nat -L, and nsenter instead of guessing.
  • Bind services to 0.0.0.0 inside the container — the default for most servers — so the published port and sibling containers can reach them, and control exposure at the publish step instead.
  • Use docker network inspect to see which containers share a bridge and what subnet they are on, since that determines who can reach whom by IP.
  • Treat a container's private IP as ephemeral and host-local — never hardcode it. Rely on names (topic 41) and published ports (topic 42) instead.
Comparable tools Podman netavark · CNI bridge plugin the same veth + bridge + iptables stack under a different driver Kubernetes CNI Calico · Cilium · Flannel program the host's networking per pod ip netns · bridge · iptables/nftables the raw Linux tools Docker is automating

Knowledge Check

Why can a process bound to 127.0.0.1 inside web not be reached from proxy?

  • Each container has its own network namespace, so each has a separate loopback device that the other cannot see
  • Docker installs an iptables firewall rule that blocks loopback traffic between containers by default for isolation
  • The two containers sit on different bridges, so their loopback interfaces cannot route across the gap between them
  • 127.0.0.1 is a privileged address that only the host's root namespace is permitted to bind

What does a veth pair connect, and where do its two ends live?

  • One end is eth0 inside the container's namespace; the other lands on the host, attached to a bridge
  • Both ends live inside two different containers' namespaces, wiring the pair of containers together directly without a bridge
  • One end is the host's physical NIC for LAN access and the other attaches to the container's writable disk
  • It connects the container's writable layer to the image's read-only layers so storage stays consistent

Why is a container's 172.17.0.2 address not reachable from another machine on the LAN?

  • It is a private bridge-subnet address behind the host's NAT, routable only on that host's bridge
  • The address is encrypted on the wire and only the local daemon holds the key needed to decrypt it
  • Reaching it from another host requires a paid Docker networking license that unlocks cross-host routing
  • A container can never send or receive any traffic outside its own host, in or out

How does the network-namespace view explain what publishing a port does?

  • Publishing installs an iptables DNAT rule that forwards a host port into the container's namespace
  • Publishing copies the container's listening process onto the host so the host kernel runs that process directly
  • Publishing deletes the container's network namespace so it falls back to using the host's network stack
  • Publishing creates a new dedicated bridge for each forwarded port to keep the traffic separated

You got correct