network_mode: host makes Docker containers share the host machine's network stack directly. Instead of the container getting its own network namespace with its own IP address, it uses the host's IP and all the host's network interfaces. Any port the container listens on is immediately accessible on the host's public IP.
This removes Docker's network isolation entirely. It's the most aggressive network setting in Docker and is almost never the right choice for a web service.
What it looks like
# docker-compose.yml with network_mode: host:
services:
myapp:
image: myapp:latest
network_mode: host
# No ports: section needed — container shares host network directly
environment:
PORT: 3000
With this config, your application listening on port 3000 inside the container is immediately listening on port 3000 on the host's public IP. No port mapping. No Docker NAT. No UFW protection. Anyone who can reach your server can reach port 3000.
Why it breaks security
Bypasses all Docker network isolation. Docker's default bridge network gives each container its own IP in a private subnet (172.17.0.0/16). Containers can't directly access each other or the host without explicit port mappings. network_mode: host eliminates this completely.
UFW rules don't protect host-mode containers. UFW manages the host's INPUT chain. When a container uses host networking, its traffic flows through the host network stack directly. A UFW deny 3000 rule may or may not apply depending on how the traffic arrives — the behavior is inconsistent and unreliable for host-mode containers.
Container can see all host network traffic. A container in host mode can bind to any port on the host, including ports already in use by other services. It can also sniff traffic on the host's network interfaces if given sufficient privileges.
The fix: use bridge networking with explicit port mapping
# Remove network_mode: host entirely.
# Use explicit port mapping instead:
services:
myapp:
image: myapp:latest
# network_mode: host <-- remove this line
ports:
- "127.0.0.1:3000:3000" # Bind to localhost only
networks:
- app-net
networks:
app-net:
driver: bridge
Binding to 127.0.0.1:3000:3000 means the container's port 3000 is only accessible on the host's loopback interface. Nginx or Traefik running on the host can proxy to it. Nothing external can reach it directly.
When network_mode: host is legitimate
There are genuine use cases, though they're rare:
- Network monitoring tools — tools that need to see all host network traffic (like a packet analyzer) legitimately need host networking
- High-frequency trading / ultra-low latency — Docker's NAT layer adds microseconds of latency that matters in HFT contexts
- Host network diagnostics — short-lived debug containers that need to see the full host network state
For a web app, API server, database, cache, or message queue — none of these apply. Use bridge networking.
Multi-container setups: use Docker networks instead
The most common reason developers reach for network_mode: host is that they can't get two containers to talk to each other. The correct solution is a shared Docker network, not host networking:
services:
app:
image: myapp:latest
networks:
- app-net
ports:
- "127.0.0.1:3000:3000"
db:
image: postgres:15
networks:
- app-net
# No ports exposed — only accessible within app-net
networks:
app-net:
driver: bridge
Containers on the same Docker network can reach each other by service name. app can connect to the database at db:5432 without any port exposure.
Paste your docker-compose.yml to detect network_mode: host, exposed ports, and other security issues automatically.
Open Docker Auditor →