Master Single‑Host Container Networking with Linux Namespaces, veth and Bridges
This tutorial walks through building isolated, virtualized networking for containers on a single Linux host using network namespaces, virtual Ethernet pairs, Linux bridges, IP routing, NAT, and iptables rules, enabling inter‑container communication, host access, and external connectivity without writing any custom code.
Using containers often feels magical; they are easy for those who understand the underlying principles but can be a nightmare for newcomers. Fortunately, containers are just isolated, limited Linux processes, and you can run them without images while building images requires running containers.
We now address single‑host container networking, answering four key questions:
How to virtualize network resources so each container thinks it has an exclusive network?
How to let containers coexist peacefully, avoid interference, and still communicate?
How to access external networks from inside a container?
How to expose a container's service to the outside world (port publishing)?
The solution relies on well‑known Linux features:
Network namespaces
Virtual Ethernet devices (veth)
Linux bridges
IP routing and NAT
No additional code is required to make this network magic work.
Prerequisites
Any Linux distribution works. All examples run on a Vagrant CentOS 8 VM:
$ vagrant init centos/8
$ vagrant up
$ vagrant ssh
[vagrant@localhost ~]$ uname -a
Linux localhost.localdomain 4.18.0-147.3.1.el8_1.x86_64We use Docker or Podman as the container runtime and focus on the most basic tools.
Network namespace isolation
Linux’s network stack consists of devices, routing rules, and netfilter hooks (iptables). A simple script inspect-net-stack.sh shows the current state:
#!/usr/bin/env bash
echo "> Network devices"
ip link
echo -e "
> Route table"
ip route
echo -e "
> Iptables rules"
iptables --list-rulesBefore running the script we create a custom chain:
$ sudo iptables -N ROOT_NSRunning the script now prints the host’s network devices, routes, and iptables rules, confirming that each future container will have its own isolated stack.
We create a network namespace using the ip netns command:
$ sudo ip netns add netns0
$ ip netns
netns0To enter the namespace we use nsenter:
$ sudo nsenter --net=/var/run/netns/netns0 bash
# inside netns0, the network stack is emptyConnecting containers with virtual Ethernet (veth)
Linux provides veth pairs that act as a tunnel between two namespaces. Create a pair:
$ sudo ip link add veth0 type veth peer name ceth0One end stays in the root namespace, the other is moved into netns0:
$ sudo ip link set ceth0 netns netns0Bring the devices up and assign IP addresses:
$ sudo ip link set veth0 up
$ sudo ip addr add 172.18.0.11/16 dev veth0
$ sudo nsenter --net=/var/run/netns/netns0 bash -c "ip link set lo up && ip link set ceth0 up && ip addr add 172.18.0.10/16 dev ceth0"Ping tests show that the two ends can reach each other:
# from netns0
ping -c 2 172.18.0.11
# from root namespace
ping -c 2 172.18.0.10Routing tables reveal a single 172.18.0.0/16 route in each namespace pointing to the peer device.
Connecting multiple containers with a Linux bridge
When more than one container is attached to the same bridge, each container can communicate with the others at L2. First, clean up the previous setup:
$ sudo ip netns delete netns0
$ sudo ip netns delete netns1
$ sudo ip link delete veth0
$ sudo ip link delete ceth0
$ sudo ip link delete veth1
$ sudo ip link delete ceth1Create two namespaces and their veth pairs without assigning IPs yet:
# netns0
sudo ip netns add netns0
sudo ip link add veth0 type veth peer name ceth0
sudo ip link set veth0 up
sudo ip link set ceth0 netns netns0
# netns1
sudo ip netns add netns1
sudo ip link add veth1 type veth peer name ceth1
sudo ip link set veth1 up
sudo ip link set ceth1 netns netns1Create a bridge and attach both veth devices:
$ sudo ip link add br0 type bridge
$ sudo ip link set br0 up
$ sudo ip link set veth0 master br0
$ sudo ip link set veth1 master br0Assign IPs inside each namespace:
# netns0
sudo nsenter --net=/var/run/netns/netns0 bash -c "ip link set lo up && ip link set ceth0 up && ip addr add 172.18.0.10/16 dev ceth0"
# netns1
sudo nsenter --net=/var/run/netns/netns1 bash -c "ip link set lo up && ip link set ceth1 up && ip addr add 172.18.0.20/16 dev ceth1"Now the containers can ping each other:
# from netns0
ping -c 2 172.18.0.20
# from netns1
ping -c 2 172.18.0.10Connecting the bridge to the host and the external world
Give the bridge an IP address so the host can reach the containers:
$ sudo ip addr add 172.18.0.1/16 dev br0Now the host can ping both containers, and the containers can ping the host.
Enable IP forwarding on the host:
sudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'Set a default route inside each namespace pointing to the bridge:
# netns0
sudo nsenter --net=/var/run/netns/netns0 bash -c "ip route add default via 172.18.0.1"
# netns1 (same command)Now containers can reach external addresses (e.g., 8.8.8.8) after applying NAT:
$ sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADEPing tests confirm internet connectivity.
Port publishing (exposing container services)
To expose a service running inside a container (e.g., a Python HTTP server on 172.18.0.10:5000) to the host’s external IP, add DNAT rules:
# traffic arriving on the host’s eth0
sudo iptables -t nat -A PREROUTING -d 10.0.2.15 -p tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000
# local traffic generated on the host
sudo iptables -t nat -A OUTPUT -d 10.0.2.15 -p tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000Enable bridge netfilter so iptables can see bridged traffic:
sudo modprobe br_netfilterAfter these rules, curl 10.0.2.15:5000 returns the container’s HTTP response.
Understanding Docker network drivers
Docker’s --network host mode shares the host’s network namespace, while --network none leaves only a loopback interface. The default --network bridge mode mirrors the bridge setup described above, allowing containers to communicate via a virtual switch.
Rootless containers and networking
Rootless containers (e.g., with Podman) cannot create veth pairs directly because that requires root privileges. They rely on slirp4netns, which provides user‑space networking via a TAP device. However, rootless containers lack raw socket capabilities, so tools like ping do not work inside them.
Conclusion
The presented approach—using network namespaces, veth pairs, Linux bridges, routing, and NAT—is one of the most common ways to build isolated container networks on a single host. Many other solutions exist, often provided by third‑party plugins, but they all depend on the same underlying Linux network virtualization primitives.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
