Container Virtual Networking: Bridge Networks, veth Pairs, and Network Namespaces
Understanding virtual patch cables, docker0 bridge gateways, and network namespace isolation.
Part 12 in Series — Catch up on the previous article: Enforcing Container Resource Limits: cgroups v1 vs v2 Memory and CPU Limits (Part 11) before diving into this post.
You launch two containers on a single Linux host:
docker run -d --name container_A alpine sleep 3600
docker run -d --name container_B alpine sleep 3600
Inspecting their network configurations shows that container_A is assigned IP 172.17.0.2 and container_B is assigned IP 172.17.0.3.
Inside container_A, you execute:
ping 172.17.0.3
The ping succeeds with sub-millisecond response times.
Yet, if you list network interfaces inside container_A, you see only a local loopback lo and a single virtual ethernet interface named eth0.
How do two isolated processes living inside completely separate Linux network namespaces send IP packets to each other across a shared host machine?
The answer lies in Virtual Ethernet (veth) Pairs and the docker0 Virtual Bridge.
1. Network Namespaces: Isolated Network Stacks
By default, every Linux container runs inside an isolated Network Namespace (CLONE_NEWNET).
A Network Namespace provides a process with its own private virtual network stack:
- Private network interfaces (
eth0,lo). - Private IP routing tables (
ip route). - Private
iptablesfirewall rules. - Private ARP neighbor tables and socket binding lists.
HOST NETWORK NAMESPACE CONTAINER NETWORK NAMESPACE
+------------------------------------+ +------------------------------------+
| Interfaces: eth0 (Host Physical IP)| | Interfaces: eth0 (172.17.0.2) |
| Routing Table: Host default GW | | Routing Table: Default via 172.17.0.1|
| Sockets: Host listening ports | | Sockets: Container local ports |
+------------------------------------+ +------------------------------------+
Because a new network namespace starts completely empty (with only a down lo loopback interface), the container engine must construct virtual hardware components to connect it to the host network.
2. Virtual Ethernet (veth) Pairs: The Virtual Patch Cable
How do you connect two separate network namespaces?
Linux provides a virtual networking device driver called Virtual Ethernet Pairs (veth).
A veth device is an interconnected pair of virtual network interfaces that act like a virtual patch cable:
- Anything transmitted into one end of the
vethpair automatically emerges out of the other end.
HOST NETWORK NAMESPACE CONTAINER NETWORK NAMESPACE
+------------------------------+ +------------------------------+
| veth1a (Attached to docker0) | <============> | eth0 (Inside Container) |
+------------------------------+ Virtual Cable +------------------------------+
When Docker launches a container:
- It creates a
vethpair (e.g.,veth1aandveth1b). - It keeps
veth1ainside the Host Network Namespace and renames it to something likeveth4a91b2c. - It moves
veth1binside the Container Network Namespace and renames it toeth0.
3. The docker0 Virtual Bridge
Having veth cables is not enough if you want to connect 50 containers together.
Docker creates a virtual Layer-2 software switch in the host network namespace named docker0.
HOST NETWORK NAMESPACE
+-------------------------------------------------------------------------+
| |
| +---------------------------+ |
| | docker0 Virtual Bridge | |
| | (Gateway: 172.17.0.1) | |
| +---------------------------+ |
| / \ |
| +----------+ +----------+ |
| | veth1a | | veth2a | |
+------------+----------+-------------------------+----------+------------+
^ | ^ |
| | Virtual Cable 1 | | Virtual Cable 2
| v | v
+--------------------------+ +--------------------------+
| eth0 (172.17.0.2) | | eth0 (172.17.0.3) |
| CONTAINER A NETNS | | CONTAINER B NETNS |
+--------------------------+ +--------------------------+
Characteristics of docker0:
- Virtual Layer-2 Switch: Connects all host
vethends together, allowing containers to broadcast ARP requests and exchange Ethernet frames. - Layer-3 Default Gateway: Assigned the IP address
172.17.0.1/16. Acts as the default gateway for all containers attached to the default bridge network.
4. Packet Traversal Trace: Container A to Container B
Let’s trace the exact path of an ICMP Echo Request (ping 172.17.0.3) sent from Container A to Container B:
[ Container A (172.17.0.2) ]
1. Application executes ping 172.17.0.3
2. Inspects local routing table: 172.17.0.3 falls within 172.17.0.0/16 subnet.
3. Sends ARP request over eth0 to find MAC address for 172.17.0.3.
|
v (Frame travels across virtual patch cable)
[ Host Namespace: veth1a ]
4. Frame emerges out of veth1a directly into docker0 bridge.
|
v
[ docker0 Virtual Bridge ]
5. docker0 acts as a Layer-2 switch. It inspects its MAC learning table,
finds that destination MAC belongs to veth2a, and forwards the frame.
|
v
[ Host Namespace: veth2a ]
6. Frame travels down veth2a cable into Container B's network namespace.
|
v
[ Container B (172.17.0.3) ]
7. Frame arrives at eth0 interface. Container B receives ICMP Echo Request
and generates ICMP Reply back along the reverse path!
Summary & Next Steps
Container virtual networking relies on Linux kernel virtual device drivers:
- Network Namespaces isolate network stacks (interfaces, routing tables, socket lists).
- Virtual Ethernet (
veth) Pairs act as virtual patch cables connecting container namespaces to the host. - The
docker0Bridge acts as an in-memory Layer-2 switch and Layer-3 gateway (172.17.0.1). - Container-to-Container communication on the same bridge occurs via Layer-2 frame switching without leaving the host host RAM.
In the next article, we examine Port Publishing Under the Hood: iptables DNAT, Userland Proxies, and Host Routing.
References & Further Reading
- Petazzoni, J. (2015). Don’t Run Docker in Docker for CI. Jpetazzo Blog.
- Linux Man Pages. tmpfs(5) & mount(8) Bind Mount Mechanics. Linux Kernel Docs.
- Docker Inc. Manage Data in Docker (Volumes vs Bind Mounts). Docker Docs.
Part 13: Port Publishing Under the Hood: iptables DNAT, Userland Proxies, and Host Routing
Continue to Part 13 →