Skip to content
RunStack
Wiki· Updated 29 Oct 2026

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": false in 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).