By default, Docker inserts its own rules into iptables to handle container networking:
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER (1 references)
target prot opt source destination
This is mostly fine, but there’s a significant caveat: when you expose a port with -p, Docker adds a rule to the DOCKER chain that accepts connections from any interface, including public-facing ones. This bypasses any DROP rules you have on the INPUT chain.
docker run -d -p 6667:6667 mbologna/docker-bitlbee
Check what Docker added to iptables:
Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.17.0.2 tcp dpt:6667
The container is now reachable on port 6667 from 0.0.0.0, every interface, including eth0 on a public-facing server. Your INPUT chain DROP policy does nothing to stop this.
Option 1: bind to a specific interface Link to heading
Bind the container’s exposed port to a specific interface when starting it:
docker run -d -p 127.0.0.1:6667:6667 mbologna/docker-bitlbee
This limits exposure to localhost only. Useful when the service is consumed locally or via a reverse proxy on the same host.
Option 2: disable Docker’s iptables manipulation entirely Link to heading
If you want full control over iptables yourself, disable Docker’s automatic rule management. Create or edit /etc/docker/daemon.json:
{
"iptables": false
}
Note: This option must be set in daemon.json; setting it in /etc/default/docker or in Docker’s systemd unit file will not work.
After restarting the Docker daemon, containers no longer receive automatic iptables rules. You take full responsibility for NAT and forwarding. At minimum, you’ll need a MASQUERADE rule to allow containers to reach the internet:
iptables -t nat -A POSTROUTING -s 172.17.0.0/24 -o eth0 -j MASQUERADE
This approach is appropriate when you’re already managing a complex iptables ruleset and don’t want Docker reaching in and reordering it.
Updated 2026: Docker has added
--iptablesdocumentation and theip6tablesequivalent over the years. If you’re running Docker with IPv6, the same issue applies to ip6tables; check"ip6tables": falseif needed. For most homelab use cases, option 1 (binding to a specific interface) is simpler and sufficient.