Octet

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)

Output
$ 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:80

That 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

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

Traps

  • Trusting ufw or INPUT rules to protect published container ports.
  • Running nft flush ruleset or a full iptables-restore on a Docker host.
How did you do?