Skip to main content
RunBook Academy

Docker & ContainersVII · NetworkingHost networking

Host networking — when to break out of isolation

Intermediate⏱ ~22 mindockerss

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

Not yet marked complete on this device.

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

Service impact possibleno eth0, no veth
$ 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 veth pair, no docker0 attachment 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 docker0 itself.

Every port the container binds is a port on the host. Publishing is neither needed nor possible.

Read-only / Safe-p is silently ignored
$ 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 bound 127.0.0.1 believing that made it private is reachable by every host-networked container on the box.
  • Port publishing. -p and ports: are discarded.
  • Embedded DNS. There is no 127.0.0.11 and no service discovery. The container uses the host’s /etc/resolv.conf, exactly as a host process does. In Compose this means db does not resolve, and the failure appears as a connection error in the application rather than anything network-shaped.
  • Per-container network accounting. docker stats reports 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/net and the container’s net namespace link print the same inode.
  • docker exec into the container finds no eth0 and no 127.0.0.11 in /etc/resolv.conf.
  • sudo ss -tlnp on the host shows the container’s process directly, by name, with no docker-proxy in 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

  1. Q1. A container is started with `--network host -p 8080:80` from an image that serves on port 80. Which port is reachable?

  2. Q2. Which namespace does `--network host` discard?

  3. Q3. A Compose stack is migrated to `network_mode: host` and the application starts failing to resolve `db`. Why?

  4. Q4. Which statements about a host-networked container are true? Select all that apply.

  5. 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.