Skip to main content

Chapter 4: Container Networking

Isolated containers need to communicate — with each other and the outside world. Let’s implement Docker-style bridge networking! Container networking can feel like the most confusing part of Docker, but it builds on concepts you already know from physical networking, just virtualized in software. A veth pair is a virtual cable with two ends: one plugged into the container, one plugged into a bridge on the host. The bridge is a virtual switch that forwards packets between containers. NAT (via iptables) translates addresses so containers with private IPs can reach the internet. If you have ever set up a home router, you have used these same concepts — your router does NAT, your ethernet switch is a bridge, and each device connected to it is like a container with its own IP.
Prerequisites: Chapter 3: Filesystem
Further Reading: AWS Networking
Time: 3-4 hours
Outcome: Containers with network connectivity

Container Networking Overview


Key Networking Concepts


Part 1: Network Manager

src/main/java/com/minidocker/network/NetworkManager.java

Part 2: Port Mapping

src/main/java/com/minidocker/network/PortMapping.java

Part 3: Container-to-Container Communication


Part 4: DNS Resolution (Container Names)

src/main/java/com/minidocker/network/DnsResolver.java

Part 5: Integrated Example


Testing Networking


Network Modes Comparison


Exercises

Add host networking mode:
Add basic network policies:
Extend port forwarding for UDP:

Key Takeaways

Veth Pairs

Virtual ethernet pairs connect container to host network

Bridge

Virtual switch connects multiple containers together

NAT

iptables MASQUERADE enables outbound connectivity

Port Forwarding

DNAT rules expose container ports on host

What’s Next?

In Chapter 5: Images, we’ll implement:
  • OCI image format
  • Image layers and manifests
  • Pulling images from registries
  • Building images

Next: Images

Learn about container image format

Interview Deep-Dive

Strong Answer:
  • The application inside the container sends a packet to its default gateway (the bridge IP, e.g., 172.17.0.1) via its eth0 interface, which is actually one end of a veth pair. The packet travels through the veth pipe to the host-side interface, which is attached to the docker0 bridge.
  • The bridge acts as a Layer 2 switch. It forwards the packet based on MAC address. Since the destination is outside the bridge network, the packet goes to the bridge interface itself (172.17.0.1), which is a Layer 3 endpoint on the host.
  • The host kernel’s routing table sends the packet toward the host’s physical interface (e.g., eth0). But first, the packet passes through the iptables POSTROUTING chain, where a MASQUERADE rule rewrites the source IP from the container’s private IP (172.17.0.2) to the host’s public IP. This is Source NAT (SNAT). Without it, the response packet would not know how to route back to the container’s private address.
  • The response packet arrives at the host’s physical interface, and the conntrack module (which tracks connection state) recognizes it belongs to the NATed connection. It rewrites the destination IP back to the container’s private IP and forwards it through the bridge and veth pair back to the container.
  • The key insight is that this is the same NAT mechanism that home routers use. Every container behind a bridge is analogous to every device behind your home router — they share a public IP and rely on connection tracking to demultiplex return traffic.
Follow-up: What happens when two containers on the same bridge communicate? Does traffic leave the host?No. The bridge handles it entirely in kernel space. Container A sends a packet to Container B’s IP. The bridge learns Container B’s MAC address from its veth port and forwards the frame directly — it never touches the host’s physical interface or any iptables NAT rules. This is pure Layer 2 switching, which is why same-bridge container communication has extremely low latency (sub-millisecond). The iptables FORWARD chain rules must allow this traffic, which is why Docker adds iptables -A FORWARD -i docker0 -o docker0 -j ACCEPT during bridge setup.
Strong Answer:
  • Port forwarding uses iptables DNAT (Destination NAT) rules in the PREROUTING chain. When you run docker run -p 8080:80, Docker adds a rule: packets arriving at host port 8080 get their destination rewritten to the container’s IP at port 80. The OUTPUT chain gets a similar rule for traffic originating from the host itself.
  • The performance cost is real but often overstated. Each packet traverses the iptables NAT table (PREROUTING or OUTPUT), gets its header rewritten, passes through the bridge, and traverses the veth pair. This adds roughly 5-15 microseconds of latency per packet compared to host networking. For most web applications, this is negligible. For high-throughput services processing millions of packets per second, the overhead accumulates.
  • Host networking (--network host) eliminates all of this. The container shares the host’s network namespace directly — no veth pairs, no bridge, no NAT. The container binds to the host’s actual network interfaces. Latency is identical to bare metal. The trade-off is zero network isolation: the container can see and bind to any host port, and port conflicts between containers become your problem.
  • In Kubernetes, this maps to the hostNetwork: true pod spec, which is commonly used for CNI plugins, ingress controllers, and monitoring agents that need direct hardware access. For application pods, Kubernetes uses CNI plugins that implement pod networking through veth pairs and overlay networks, similar to Docker’s bridge model but cluster-wide.
Follow-up: At massive scale with thousands of containers, what networking problems emerge that you would not see at small scale?The biggest issue is iptables rule explosion. Docker adds several iptables rules per container (NAT, filter, and sometimes mangle). With thousands of containers, the iptables rule set becomes enormous, and rule evaluation becomes linear — every packet checks every rule until a match. This can add milliseconds of latency per packet. The solution is IPVS (IP Virtual Server), which uses hash tables instead of linear rule chains, or eBPF-based solutions like Cilium that bypass iptables entirely. The second issue is ARP table exhaustion: thousands of containers on a single bridge generate thousands of ARP entries, which can overflow the kernel’s neighbor table (tuned via net.ipv4.neigh.default.gc_thresh3). The third is bridge MAC table overflow, causing the bridge to flood packets to all ports instead of switching to the correct one.
Strong Answer:
  • Docker’s bridge model gives each container a private IP within a single host. Containers on different hosts cannot communicate by IP without additional overlay networking. Port forwarding is required for external access, and container IPs are ephemeral and host-scoped.
  • Kubernetes mandates a flat networking model: every pod gets a cluster-routable IP, and every pod can reach every other pod by IP without NAT. This eliminates the port-mapping complexity entirely — a service listening on port 80 in a pod is accessible at that pod’s IP on port 80 from anywhere in the cluster.
  • The problem this solves is service discovery and load balancing at scale. In Docker’s model, you need to track which host a container is on and which port it is mapped to. In Kubernetes, you address pods by IP (or more commonly, by Service DNS name), and the networking layer handles the rest. This is what makes horizontal scaling trivial — spin up more pods, and the Service load balancer automatically includes them.
  • The implementation varies by CNI plugin: Calico uses BGP to distribute pod routes across nodes; Flannel uses VXLAN overlays; Cilium uses eBPF for packet forwarding. Each has different performance characteristics, but they all provide the same flat-network abstraction.
Follow-up: What is the overhead of overlay networks like VXLAN compared to native routing?VXLAN encapsulates Layer 2 frames inside UDP packets, adding a 50-byte header overhead per packet and requiring encapsulation/decapsulation at each hop. This adds roughly 10-20% throughput reduction and 100-200 microseconds of latency compared to native routing. Calico with BGP avoids this by distributing routes natively — packets are routed at Layer 3 without encapsulation, achieving near-bare-metal performance. The trade-off is that BGP requires network infrastructure support (routers that peer with the nodes), while VXLAN works on any network. For latency-sensitive applications, the choice of CNI plugin can be as impactful as the choice of programming language.