Docker networking is built on standard Linux kernel features. Nothing Docker-specific in the kernel — it’s all primitives that existed before Docker. Understanding them makes networking behaviour predictable rather than magical.


The Core Building Blocks

1. Virtual Ethernet Pairs (veth)

When a container starts, the kernel creates a veth pair — two virtual network interfaces connected like opposite ends of a pipe. Whatever goes in one end comes out the other:

container end: eth0  ↔  host end: vethXXXXXX

The container end (eth0) is moved into the container’s net namespace, so the container sees it as its only NIC. The host end stays in the host namespace.

2. Linux Bridge (docker0)

The host ends of all veth pairs are plugged into a Linux bridgedocker0 by default. A bridge is a virtual Layer 2 switch implemented entirely in the kernel. It forwards Ethernet frames between attached interfaces based on MAC addresses, just like a physical switch.

        docker0 (bridge, 172.17.0.1)
        /            \           \
vethAAA          vethBBB      vethCCC
   |                 |             |
container1       container2    container3
(172.17.0.2)    (172.17.0.3)  (172.17.0.4)

Containers on the same bridge can reach each other through it. The bridge’s own IP (172.17.0.1) becomes the default gateway for containers.

3. iptables / netfilter

The kernel’s netfilter framework (managed via iptables) handles two jobs:

  • NAT (masquerade) — when a container sends traffic to the internet, iptables rewrites the source IP from the container’s private IP to the host’s public IP. Replies are translated back. Standard SNAT, exactly like a home router.
  • Port mapping-p 8080:80 is a DNAT rule: packets arriving at the host on port 8080 get their destination rewritten to 172.17.0.x:80 before hitting the bridge.

Docker writes these iptables rules automatically when containers start.

4. Network Namespaces

Each container gets its own net namespace — its own private view of the network stack: interfaces, routing table, iptables rules, ports. Port 80 inside one container doesn’t conflict with port 80 inside another because they’re in completely separate namespaces.


A Packet’s Journey

Container to internet:

container1 eth0
    → vethAAA (veth pair)
        → docker0 (bridge)
            → host routing table
                → iptables MASQUERADE (SNAT: rewrite src IP)
                    → host's physical NIC
                        → internet

Host to container on mapped port 8080:

host:8080
    → iptables DNAT (rewrite dst to 172.17.0.2:80)
        → docker0 (bridge)
            → vethAAA (veth pair)
                → container1 eth0:80

The Network Modes at OS Level

ModeOS mechanism
Bridge (default)veth pair + Linux bridge + iptables NAT
HostNo net namespace created — container shares host’s network stack directly
Overlayveth + bridge + VXLAN tunnel (encapsulates L2 frames in UDP across hosts)
NoneNet namespace created but nothing attached — loopback only

Host mode is the simplest: the container doesn’t get a net namespace. Processes bind directly to the host’s interfaces. Zero isolation, zero overhead. Useful when you need maximum network performance or a tool that must see the host’s real interfaces.

Overlay adds VXLAN: a kernel feature that wraps Ethernet frames in UDP and ships them between hosts, making containers on different machines appear to be on the same L2 network. This is what Docker Swarm uses.


DNS Resolution Inside Containers

When you create a custom network (or use Docker Compose), Docker runs an embedded DNS server at 127.0.0.11 inside each container’s network namespace. Container names and service names resolve to their current IP addresses automatically — no /etc/hosts maintenance required.

This is why in a Compose file you can use DB_HOST=db and it just works: the db service name resolves to whatever IP the db container currently has.

The default docker0 bridge does not have this DNS — containers can reach each other only by IP. This is one of the main reasons to always use custom networks (or Compose, which creates one automatically).

# Custom network — names resolve
docker network create mynet
docker run -d --name db --network mynet postgres:15
docker run --network mynet alpine ping db    # works
 
# Default bridge — names do not resolve
docker run -d --name db postgres:15
docker run alpine ping db    # fails

Port Mapping

Port mapping is the mechanism for making a container’s port accessible from outside the host.

docker run -p 8080:80 nginx          # all interfaces on host, port 8080 → container port 80
docker run -p 127.0.0.1:8080:80 nginx  # loopback only — not accessible from outside the host

Binding to all interfaces (0.0.0.0) is the default and makes the port accessible from any network the host is on. Binding to 127.0.0.1 restricts it to the host machine — useful in production where a reverse proxy (nginx, Caddy) sits in front and the app port shouldn’t be publicly reachable.


One sentence: a Docker network is a Linux bridge with veth pairs connecting container net namespaces to it, and iptables rules handling NAT and port mapping — all standard kernel primitives, no Docker-specific kernel code involved.


See Also