A classic server scare: you lock down the firewall to 80 and 443, start docker compose, and your database or internal API is reachable from the internet. The firewall is not broken; that is how Docker works.
Why it happens
When you publish a port (ports: - "5432:5432"), Docker adds its own networking rules to route that traffic to the container. That traffic is routed before the input rules you configured with firewalld or ufw are evaluated, so your “deny” rule never applies.
Test from another machine, not from the server itself:
nc -zv SERVER_IP 5432
Golden rule: only publish what must be public
Most services (databases, internal APIs, Redis) don’t need published ports. Containers on the same Compose network talk by service name:
services:
api:
environment:
MONGO_URI: mongodb://mongo:27017/app
mongo:
image: mongo:7
# no "ports": unreachable from outside Docker's network
If a reverse proxy running on the host needs to reach a service, publish it on loopback only:
ports:
- "127.0.0.1:3000:3000"
When 127.0.0.1 doesn’t work
On some RHEL-family servers with firewalld, loopback-published ports can become unreachable even from the host. Check:
- Did you reload firewalld after starting Docker? A reload can wipe Docker’s rules. Restart Docker afterwards:
sudo firewall-cmd --reload && sudo systemctl restart docker. - Is Docker’s userland proxy disabled in
/etc/docker/daemon.json? Loopback traffic then depends on other kernel settings. - Cleanest alternative: publish nothing and run the proxy (Nginx, Caddy, Traefik) as a container on the same network. Only the proxy publishes 80 and 443.
“Port is already allocated” with override files
With a base docker-compose.yml plus a docker-compose.prod.yml, port lists are merged, not replaced, so you end up binding the same port twice. Since Compose v2.24 you can replace the list:
services:
frontend:
ports: !override
- "127.0.0.1:8100:80"
Checklist
- Internal services publish no ports.
- Services behind a host proxy are published on
127.0.0.1. - Verified from outside the server with
ncornmap. - Docker restarted after firewall changes.
- Production overrides use
!overrideforports.
Not sure what your production server exposes? I can audit it.