Docker and the firewall
Ports from docker run -p and compose ports are open to the internet even with ufw deny. How to close them: 127.0.0.1, DOCKER-USER, allowlists.
In short: a port published by Docker (-p 5432:5432, ports: ["5432:5432"]) is open to the whole internet, even if ufw status says it is closed.
Check your server with the rules analyzer (paste iptables-save) or from outside with the port checker.
Why
A packet to a container does not go to the INPUT chain where ufw rules live, but through NAT to FORWARD:
internet → PREROUTING (nat: DOCKER, DNAT 203.0.113.5:5432 → 172.17.0.2:5432)
→ FORWARD → DOCKER-USER → DOCKER-FORWARD / DOCKER → container
(INPUT and ufw never see this packet)
Docker adds accepting rules to FORWARD for every published port itself. ufw knows nothing about them.
How to close it — from simple to flexible
1. Do not publish to the outside. If only nginx or another app on the same server talks to the service:
services:
db:
ports:
- "127.0.0.1:5432:5432" # this server only
Better still, no ports: at all — containers in one compose network reach each other by service name.
2. Allow only your addresses — the DOCKER-USER chain. Docker leaves it alone, and it is checked before Docker's own rules:
# let 203.0.113.10 reach port 5432 of containers, nobody else
iptables -I DOCKER-USER -p tcp -m conntrack --ctorigdstport 5432 --ctdir ORIGINAL ! -s 203.0.113.10 -j DROP
--ctorigdstport is the port the client connected to (before DNAT). Save: netfilter-persistent save. Rules in DOCKER-USER survive Docker restarts.
3. Close everything published except what you need:
iptables -I DOCKER-USER -i eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
iptables -I DOCKER-USER 2 -i eth0 -p tcp -m conntrack --ctorigdstport 443 --ctdir ORIGINAL -j RETURN
iptables -A DOCKER-USER -i eth0 -j DROP
eth0 is the external interface (ip route get 1.1.1.1 shows its name).
4. Restrict at the application level. For web services — allow/deny in the nginx in front of the container; for RunStack services — rs <service> access "203.0.113.10".
What not to do
"iptables": falsein daemon.json — Docker stops managing rules and container networking and internet access break.- Edit the DOCKER and DOCKER-FORWARD chains by hand — Docker rewrites them on restart.
- Rely on
ufw deny 5432— see above.
IPv6
If the server has IPv6 and IPv6 is enabled in Docker, do the same in ip6tables (it has its own DOCKER-USER).
