Docker & ContainersVII · NetworkingHost networking
Host networking — when to break out of isolation
What you'll learn
- State exactly which namespace --network host discards and which it keeps
- Predict the port-collision behaviour and where the error surfaces
- Explain why service discovery and -p stop working
- Recognise that host-networked ports ARE governed by the INPUT chain
- Decide when the isolation loss is justified
Prerequisites
Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
The default bridge adds a translation step between container and host. For a
small number of workloads that step is genuinely the bottleneck.
--network=host removes it — not by optimising it, but by deleting the
network namespace the container would otherwise have had.
That single sentence contains everything surprising about this driver. The
container is not “on the host network”. It is in the host’s network
namespace, which is the same namespace sshd is in.
What host networking means
$ docker run -d --name hostnet --network host nginx:1.27-alpine
# The container's interfaces ARE the host's interfaces
docker exec hostnet ip -brief addr
# And the namespace identifiers match
readlink /proc/1/ns/net
PID=$(docker inspect --format '{{.State.Pid}}' hostnet)
sudo readlink "/proc/$PID/ns/net"lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 203.0.113.10/24 fe80::5054:ff:fe12:3456/64
docker0 DOWN 172.17.0.1/16
---
net:[4026531841]
net:[4026531841]Illustrative output
Two facts fall out of the matching inode numbers:
- There is no
vethpair, nodocker0attachment for this container, and no DNAT. There is nothing to translate. - The container can see and bind every interface on the host, including
the management NIC, a VPN tunnel, and
docker0itself.
Every port the container binds is a port on the host. Publishing is neither needed nor possible.
$ docker run -d --name hn2 --network host -p 8080:80 nginx:1.27-alpine
docker ps --filter name=hn2 --format '{{.Names}} {{.Ports}}'
sudo ss -tlnp | grep ':80 'WARNING: Published ports are discarded when using host network mode
hn2
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=3122,fd=6))Illustrative output
The warning is printed once, at create time, to the client’s stderr. In a
CI log or a docker compose up -d it scrolls past. What you are left with is
a service on the port the image was built to use, not the port you thought
you had chosen — which is the same class of surprise as a firewall you
believe is filtering.
What you give up
- Port isolation. Two containers binding the same port is now a collision, not two independent namespaces.
- Container-to-container isolation. Any host-networked container reaches
any other via
localhost, and reaches every bridge gateway too. A service that bound127.0.0.1believing that made it private is reachable by every host-networked container on the box. - Port publishing.
-pandports:are discarded. - Embedded DNS. There is no
127.0.0.11and no service discovery. The container uses the host’s/etc/resolv.conf, exactly as a host process does. In Compose this meansdbdoes not resolve, and the failure appears as a connection error in the application rather than anything network-shaped. - Per-container network accounting.
docker statsreports zero network I/O, because the counters it reads belong to a namespace the container does not have. Not “small numbers” — zeros. - Overlay networks. Cross-host communication is the host’s problem again.
- Portability. The container is pinned to this host’s addressing.
When host networking is right
- The workload is a network function — a packet sniffer, a software load balancer, a DHCP relay — that must see the host’s interfaces to do its job at all. This is a correctness requirement, not a performance one.
- The workload needs raw sockets or protocol-level access that a bridge cannot represent.
- The container is the only one of its kind on the host, so the collision risk is genuinely zero.
- Measured, not assumed: the bridge’s per-packet cost is the profile’s top entry. For most services it is not — a typical HTTP service is bound by application work, and the NAT hop is noise beside it.
When host networking is wrong
- Multi-tenant hosts. The isolation loss is not scoped to the container that asked for it.
- Anything in a Compose stack that expects to reach a peer by service name. Service discovery is gone.
- Anything you might want to run two copies of.
- As a fix for a networking problem you have not diagnosed. It makes a class of symptoms disappear by removing the subsystem that produced them, which is not the same as fixing anything.
A common pattern: monitoring agents
Exporters use --network host because they measure the host, and because a
well-known port is part of their contract with the scraper.
# docker-compose.yml
services:
node-exporter:
image: prom/node-exporter:v1.8.2
network_mode: host
pid: host
command:
- '--path.rootfs=/host'
- '--web.listen-address=127.0.0.1:9100'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
volumes:
- /:/host:ro,rslave
pid: host lets the agent see host processes for process metrics.
--web.listen-address=127.0.0.1:9100 is the part usually left out: without
it the exporter binds 0.0.0.0 and publishes a full inventory of the host —
kernel version, filesystems, interfaces, uptime — to anything that can reach
port 9100. Bind it to loopback, or to the management address, and scrape it
from there.
Note the $$ in the exclude regex. Compose performs variable interpolation
on the file, so a literal $ has to be doubled. A single $ here silently
produces a different regular expression.
Sanity check
readlink /proc/1/ns/netand the container’snetnamespace link print the same inode.docker execinto the container finds noeth0and no127.0.0.11in/etc/resolv.conf.sudo ss -tlnpon the host shows the container’s process directly, by name, with nodocker-proxyin front of it.- Every host port taken by a host-networked container appears in your host’s port allocation record.
Knowledge check
Knowledge check · 5 questions
Q1. A container is started with `--network host -p 8080:80` from an image that serves on port 80. Which port is reachable?
Q2. Which namespace does `--network host` discard?
Q3. A Compose stack is migrated to `network_mode: host` and the application starts failing to resolve `db`. Why?
Q4. Which statements about a host-networked container are true? Select all that apply.
Q5. Two host-networked containers binding the same port produce a successful `docker run`, after which the second container exits on its own.
Passing score: 75%. Answers are checked in this browser.