How does Docker's default networking work on a Linux host, and what does it do to your firewall?
Show the answer
Say this first
Each container gets its own network namespace with an eth0 that is one end of a veth pair. The other end
is attached to the docker0 bridge, which holds the gateway 172.17.0.1/16. Docker enables ip_forward,
adds a MASQUERADE rule so containers reach the outside, and adds DNAT rules in a DOCKER chain for every
published port. Because published ports are DNATed, their traffic goes through FORWARD, not INPUT, so host
firewall rules on INPUT (ufw's default) do not protect them. Your own rules belong in DOCKER-USER.
Go deeper
What Docker installs (excerpt)
$ sudo iptables -t nat -S
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80That last rule is docker run -p 8080:80. In the filter table Docker sets the FORWARD policy to DROP and
jumps to DOCKER-USER before its own chains. The chain layout has changed between releases, but
DOCKER-USER is the documented, stable place for your rules.
Restricting a published port
iptables -I DOCKER-USER -i ens192 ! -s 10.0.50.0/24 -p tcp -m conntrack --ctorigdstport 8080 -j DROP--ctorigdstport matches the port before DNAT, because by the time FORWARD runs the destination port is
already 80. Or publish only on loopback with -p 127.0.0.1:8080:80.
User-defined networks
docker network create makes a br-<id> bridge with its own subnet and MASQUERADE rule, and containers on
it get Docker's embedded DNS at 127.0.0.11 for name resolution between containers.
They will ask next
Containers cannot reach a corporate subnet that starts with 172.17 or 172.18. Why?
The host has connected routes for its Docker bridges, so traffic to an overlapping corporate range never leaves the host. Move Docker's ranges with
bipanddefault-address-poolsin/etc/docker/daemon.json.How do you look at a container's interfaces from the host?
Enter its network namespace through its PID:
nsenter -t $(docker inspect -f '{{.State.Pid}}' web) -n ip addr.ip netns listdoes not show Docker's namespaces because Docker does not create the bind mounts that command reads.You reload your firewall and containers lose internet access. Why?
A tool that restores a full saved ruleset (iptables-restore without
--noflush,nft flush ruleset) removed Docker's chains, including the MASQUERADE rule. Restarting Docker rebuilds them; the long-term fix is to stop the reload from owning tables Docker uses.
Traps
- Trusting ufw or INPUT rules to protect published container ports.
- Running
nft flush rulesetor a fulliptables-restoreon a Docker host.