Skip to content

Host, none, and overlay

The bridge driver is the default because it's the right answer most of the time. The other modes exist for cases where its two costs — a NAT hop and a namespace boundary — are either unaffordable or actively in the way.

docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
9f1a2b3c4d5e   bridge    bridge    local
1a2b3c4d5e6f   host      host      local
2b3c4d5e6f7a   none      null      local

Three networks exist on a fresh install, and SCOPE is the column that matters later: local means the network exists on this host only.

--network host: no namespace at all

docker run -d --network host nginx

The container gets no network namespace of its own. It uses the host's — the host's interfaces, the host's addresses, the host's routing table, the host's ports.

Nothing is published, because there's nothing to publish to. nginx binds port 80 and that is the host's port 80:

sudo ss -tlpn sport = :80
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0      511            0.0.0.0:80      0.0.0.0:*   users:(("nginx",pid=9214,fd=6))

The process shows as nginx on the host, with a normal host PID, listening on every host address. -p is silently ignored in this mode, which produces a memorable confusion: a docker run --network host -p 8080:80 that "ignores the port mapping." It isn't ignoring it; there's no NAT layer left for it to configure.

What you gain is the removal of a hop. No DNAT, no bridge, no veth traversal — measurable for a proxy handling six-figure packet rates, irrelevant for almost everything else. What you lose is worth more than most people expect:

  • Port conflicts come back. Two containers can't both bind 80. The isolation that let you run ten copies of the same image is gone.
  • Every listening socket is exposed on every host interface, including public ones. A container that binds 0.0.0.0:6379 on a cloud VM has published Redis to the internet, and there's no -p 127.0.0.1: to prevent it.
  • It's Linux-only. On Docker Desktop the containers run inside a Linux VM, so "the host" is that VM, not your laptop — a difference that makes host mode behave completely differently in development than in production.

The legitimate uses are narrow: a container that needs to see the host's real interfaces (a monitoring agent reading NIC statistics, a DHCP or mDNS service that depends on broadcast traffic), or a network appliance where the extra hop genuinely matters. Reaching for it because port mapping is confusing is a bad trade.

--network none: a stack with nothing in it

docker run --rm --network none alpine ip -br addr
lo               UNKNOWN        127.0.0.1/8

A namespace with loopback and nothing else. No veth, no route, no way in or out.

This is the strongest isolation Docker offers and it's genuinely useful for batch work that touches only mounted files — an image resizer, a video transcoder, a build step. If the process is compromised, it has no network to exfiltrate anything over.

It's also a useful diagnostic. When you suspect a container is making network calls it shouldn't, running it with --network none turns silent background traffic into loud, obvious errors.

Overlay: containers across multiple hosts

Everything so far breaks the moment you have two machines. Both hosts hand out 172.17.0.2. Neither host's bridge knows anything about the other's containers. There's no route between them, and no way to write one that isn't ambiguous.

The overlay driver solves this by tunnelling. Container traffic is encapsulated in VXLAN — the original Ethernet frame is wrapped in a UDP packet, sent across the physical network to the other host, unwrapped, and delivered to the destination container's bridge. The underlying network only ever sees ordinary UDP between two host addresses; the containers see a flat Layer 2 network they appear to share.

docker swarm init
docker network create --driver overlay --attachable appnet
docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
9f1a2b3c4d5e   bridge    bridge    local
kx82n1p4qw7v   appnet    overlay   swarm
1a2b3c4d5e6f   host      host      local

SCOPE: swarm is the difference — this network's definition is shared across every node in the cluster, and a container attached to it on host A can reach one on host B by name.

Two properties of tunnelling are worth carrying forward, because they show up again in Kubernetes with different names:

Encapsulation costs MTU. A VXLAN header is 50 bytes on top of the original frame. If the physical network's MTU is 1500 and the container thinks its MTU is also 1500, a full-size packet becomes 1550 bytes on the wire and has to be fragmented — or, worse, gets dropped by something that won't fragment. Overlay networks therefore run a smaller MTU inside, and when that isn't configured correctly you get the classic signature: small requests work, large responses hang. Everything from the MTU and fragmentation articles applies directly, and this is where it stops being theoretical.

The underlay has to allow the tunnel. Docker's documentation lists three requirements between participating hosts: UDP 4789 for the overlay traffic itself, TCP and UDP 7946 for node-to-node communication, and TCP 2377 for the swarm control plane. A firewall or cloud security group that blocks any of those produces containers that start normally, join the network normally, and can't talk — with no error anywhere, because from the container's point of view its packets are simply lost.

Diagnose an overlay from the underlay

When containers on an overlay can't reach each other, testing between containers tells you nothing useful — the failure is one layer down. Test the hosts instead:

nc -zvu -w 3 <other-node-ip> 4789
sudo tcpdump -i eth0 -nn 'udp port 4789' -c 10

If you see VXLAN packets leaving one host and nothing arriving at the other, the problem is a firewall or security group between them, and no amount of container-level debugging will find it.

Choosing between them

Mode Isolation Reachable from outside Multi-host Reach for it when
User-defined bridge Own namespace, own subnet Only via published ports No Almost always
Default bridge Own namespace, shared subnet Only via published ports No Never deliberately — it has no DNS
host None Every port the process binds No The extra hop genuinely matters, or the container needs the host's real interfaces
none Total Not at all No Batch work that shouldn't have network access
overlay Own namespace Via published ports on any node Yes Containers on different hosts must talk directly

The honest summary is that a user-defined bridge covers the overwhelming majority of single-host work, and once you need more than one host, an orchestrator's networking replaces this decision entirely.

Practice

  1. Run the same image with --network bridge and --network host, and compare ip -br addr inside each. Then check ss -tlpn on the host in both cases and explain what changed.
  2. Try to start two containers in host mode that both bind port 8080. Note the error, and say which layer produced it.
  3. Run a container with --network none and attempt to install a package inside it. Then run the same image on a bridge network and compare.
  4. On a two-node swarm (two VMs are enough), create an overlay network, run a container on each node, and confirm they can reach each other by name. Then capture UDP 4789 on one node while they talk.
  5. On that same overlay, compare ip link show inside a container with the host's MTU. Work out the largest payload that fits without fragmentation, and test it with ping -M do -s.

Exercise 5 is the bridge to what comes next. Kubernetes takes overlay networking as a baseline assumption — every pod gets a routable address on a cluster-wide network — and then adds something the Docker model has no equivalent for: a stable virtual address that outlives the containers behind it.

Sources