Docker writes its own iptables rules, so a port published with -p is reachable from the internet even when UFW denies it. Here is how to see the problem and close it properly.
Full guide: Full firewall guide (UFW, iptables, nftables)
1. See the problem
Publish a test port, deny it in UFW, then test from ANOTHER machine. It still connects.
docker run -d --name test -p 8080:80 nginx
sudo ufw deny 8080
# from another machine:
nc -zv your_server_ip 8080
2. Simplest fix: publish on localhost only
Bind the port to 127.0.0.1 and put a reverse proxy (Nginx, Caddy, Nginx Proxy Manager) in front for public traffic.
docker run -d -p 127.0.0.1:8080:80 nginx
# docker-compose.yml
ports:
- "127.0.0.1:8080:80"
3. Or control Docker ports with UFW rules
The ufw-docker helper adds rules to Docker's DOCKER-USER chain so ufw route rules decide what is exposed.
sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
sudo chmod +x /usr/local/bin/ufw-docker
sudo ufw-docker install
sudo systemctl restart ufw
sudo ufw route allow proto tcp from any to any port 80
4. Verify from outside
Ports you did not allow must now be closed from another machine.
nc -zv your_server_ip 8080
5. Clean up the test
docker rm -f test
sudo ufw delete deny 8080
6. Troubleshooting
Containers lose internet after a change: do not set Docker's "iptables": false, it breaks container networking. Rules disappear after a reboot: make sure UFW is enabled and the DOCKER-USER rules are in /etc/ufw/after.rules (ufw-docker install does this).
sudo iptables -L DOCKER-USER -n
FAQ
Why doesn't ufw status show the problem?
UFW only shows its own rules. Docker's NAT rules run before them, so always test with a real connection from another machine.
Is binding to 127.0.0.1 enough?
For services behind a reverse proxy, yes. The port is then only reachable from the server itself.
Further reading: Docker docs: packet filtering and firewalls