Es de los sustos más comunes al montar un servidor: configuras el firewall, cierras todo menos 80 y 443, levantas tu docker compose… y resulta que la base de datos o la API quedaron accesibles desde internet. No es un error tuyo en el firewall: es como funciona Docker.
Por qué pasa
Cuando publicas un puerto (ports: - "5432:5432"), Docker agrega sus propias reglas de red para redirigir ese tráfico al contenedor. Ese tráfico se enruta hacia el contenedor antes de pasar por las reglas de entrada que configuraste con firewalld o ufw, así que tu regla de “bloquear” nunca se evalúa.
Compruébalo desde otra máquina, no desde el mismo servidor:
# Desde tu laptop, no desde el servidor
nc -zv IP_DEL_SERVIDOR 5432
Si conecta, ese puerto está expuesto aunque el firewall diga lo contrario.
Regla de oro: publica solo lo que debe ser público
La mayoría de los servicios (base de datos, API interna, Redis) no necesitan publicar puertos. Los contenedores de la misma red de Compose se hablan por nombre de servicio:
services:
api:
build: ./backend
environment:
MONGO_URI: mongodb://mongo:27017/app # "mongo" es el nombre del servicio
mongo:
image: mongo:7
# sin "ports": nadie fuera de la red de Docker puede entrar
Si necesitas llegar a un servicio desde el propio servidor (por ejemplo, un Nginx instalado en el host que hace de proxy), publícalo solo en loopback:
services:
api:
ports:
- "127.0.0.1:3000:3000"
Así el puerto existe en localhost del servidor, pero no hacia afuera.
Cuando 127.0.0.1 no funciona
En algunos servidores (lo he visto en distribuciones tipo RHEL con firewalld) publicar en 127.0.0.1 puede dejar el servicio inaccesible incluso desde el propio host. Antes de rendirte, revisa:
-
¿Recargaste firewalld después de levantar Docker?
firewall-cmd --reloadpuede borrar las reglas que Docker creó. Reinicia Docker después de recargar el firewall:sudo firewall-cmd --reload && sudo systemctl restart docker -
¿Está activo el proxy de usuario de Docker? Si en
/etc/docker/daemon.jsondesactivasteuserland-proxy, el tráfico por loopback depende de otras reglas del kernel. -
La alternativa más limpia: no publiques nada y mete el proxy (Nginx, Caddy o Traefik) como contenedor en la misma red. Solo el proxy publica 80 y 443.
services:
proxy:
image: nginx:stable
ports: ["80:80", "443:443"]
api:
build: ./backend # sin ports
El otro error: “port is already allocated” con archivos override
Si usas un docker-compose.yml base y un docker-compose.prod.yml encima, los puertos se suman, no se reemplazan. Terminas pidiendo el mismo puerto dos veces y Docker falla con port is already allocated.
Desde Compose v2.24 puedes indicar que el override reemplace la lista:
# docker-compose.prod.yml
services:
frontend:
ports: !override
- "127.0.0.1:8100:80"
También existe !reset para vaciar un valor heredado.
Checklist rápido
- Ningún servicio interno publica puertos.
- Lo que va detrás de un proxy en el host se publica en
127.0.0.1. - Probaste desde fuera del servidor con
nconmap. - Después de tocar el firewall, reiniciaste Docker.
- Los overrides de producción usan
!overrideenports.
Si tu servidor ya está en producción y no estás seguro de qué quedó expuesto, te ayudo a revisarlo.