Cómo solucionar la brecha de Docker con UFW en Ubuntu (Bypass de iptables)
Configuras tu nuevo servidor VPS con Ubuntu en Hetzner o DigitalOcean. Instalas UFW, ejecutas sudo ufw default deny incoming, abres únicamente el puerto 22 para SSH y activas el cortafuegos con sudo ufw enable. Todo parece blindado: sudo ufw status muestra que el puerto 5432 (PostgreSQL) o 3306 (MySQL) no está permitido.
Minutos después, levantas un contenedor de base de datos con Docker:
docker run -d -p 5432:5432 --name postgres-prod postgres:16
Crees que UFW está protegiendo el puerto. No es así. Desde cualquier punto de internet, cualquier escáner de puertos puede conectarse directamente a tu base de datos en el puerto 5432. Tu cortafuegos ha sido completamente ignorado.
Por qué Docker ignora las reglas de UFW
Para entender el fallo, hay que mirar cómo interactúan Docker y el filtro de paquetes de Linux (iptables / netfilter).
UFW es una capa de abstracción simplificada sobre iptables. Por defecto, cuando añades reglas con UFW (ufw allow, ufw deny), estas se insertan en la cadena INPUT. La cadena INPUT solo procesa paquetes cuyo destino final es el propio host (la interfaz de red local del sistema operativo).
Sin embargo, Docker utiliza interfaces de red virtuales (típicamente docker0 o redes puente bridge). Cuando mapeas un puerto con -p 5432:5432, Docker inserta reglas automáticas de NAT en la cadena de PREROUTING que desvían el tráfico hacia la cadena FORWARD antes de que UFW y su cadena INPUT puedan evaluarlo.
Petición entrante desde Internet (eth0:5432)
↓
[PREROUTING (NAT)] → Docker redirige hacia el contenedor (172.17.0.2:5432)
↓
[FORWARD] → UFW no filtra esta cadena por defecto; Docker la acepta
↓
[Contenedor Docker] → ¡Puerto accesible públicamente!
(La cadena [INPUT] donde vive UFW nunca llega a ver este paquete)
El resultado es que Docker tiene precedencia absoluta sobre UFW, dejando expuestos servicios críticos sin mostrar ningún aviso en la terminal.
Solución 1: Vincular el puerto exclusivamente a localhost (Recomendada)
Si el servicio en Docker (PostgreSQL, Redis, RabbitMQ, Elasticsearch) solo necesita ser consumido por otras aplicaciones en el mismo servidor o por un reverse proxy (como Nginx o Traefik), la solución más limpia y segura es especificar la IP de bind 127.0.0.1:
En comando Docker CLI:
En lugar de -p 5432:5432, escribe:
docker run -d -p 127.0.0.1:5432:5432 --name postgres-prod postgres:16
En Docker Compose (docker-compose.yml):
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432" # Solo accesible desde el host local
environment:
POSTGRES_PASSWORD: mi_password_seguro
Con este cambio, Docker solo abrirá el socket en la interfaz de loopback lo. Nadie desde la IP pública del VPS podrá alcanzar el servicio.
Solución 2: Usar la cadena DOCKER-USER en UFW
Si necesitas publicar un puerto a través de Docker en la IP pública pero quieres que UFW controle qué IPs remotas pueden conectarse (por ejemplo, permitir acceso a PostgreSQL solo desde la IP de tu oficina o servidor de backend), Docker reserva una cadena especial llamada DOCKER-USER.
Esta cadena se ejecuta antes que las reglas internas de Docker en la tabla FORWARD.
Abre el archivo de reglas posteriores de UFW:
sudo nano /etc/ufw/after.rules
Añade el siguiente bloque al final del archivo (antes de la línea COMMIT):
# Bloquear acceso externo a contenedores Docker excepto tráfico establecido
*filter
:DOCKER-USER - [0:0]
:ufw-user-forward - [0:0]
# Permitir conexiones ya establecidas
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Permitir acceso al puerto 5432 únicamente desde tu IP autorizada
-A DOCKER-USER -m conntrack --ctorigdstport 5432 -s 203.0.113.45 -j ACCEPT
# Bloquear todo el resto del tráfico entrante desde la interfaz pública (eth0)
-A DOCKER-USER -i eth0 -j DROP
COMMIT
(Nota: Sustituye eth0 por tu interfaz de red pública si es diferente, verificándolo con ip route).
Aplica los cambios recargando UFW:
sudo ufw reload
Ahora, cualquier petición hacia el puerto de Docker desde una IP no autorizada será descartada en DOCKER-USER antes de alcanzar el contenedor.
Lo que NO debes hacer: iptables: false
Muchos tutoriales antiguos recomiendan desactivar la gestión de iptables en Docker añadiendo { "iptables": false } en /etc/docker/daemon.json.
No lo hagas. Desactivar iptables en Docker rompe la resolución DNS interna entre contenedores, anula el aislamiento de redes y deja a los contenedores sin salida a internet a menos que escribas manualmente complejas reglas de SNAT y MASQUERADE.
Cómo auditar si tienes puertos de Docker expuestos ahora mismo
En flotas con múltiples proyectos y archivos docker-compose.yml, es muy fácil que un desarrollador olvide el prefijo 127.0.0.1: y publique inadvertidamente una base de datos.
Puedes comprobar manualmente tus puertos en escucha con:
sudo ss -tulpn | grep LISTEN
Si ves 0.0.0.0:5432 o :::5432 asociado al proceso docker-proxy, ese puerto está abierto a todo internet.
Detección continua con SecuryBlack y FerroSentry
Para evitar sorpresas a las 3:00 de la madrugada, nuestro agente de seguridad open source FerroSentry incluye un módulo específico de auditoría de firewall que:
- Cruza los puertos expuestos por Docker en
iptablescontra las reglas activas de UFW. - Emite una alerta crítica inmediata si detecta un bypass de Docker-UFW.
- Te entrega el comando exacto de remediación para que no tengas que pelearte con la sintaxis de iptables.
Todo ejecutado en menos de 15 MB de RAM y sin modificar nada en tu servidor sin tu consentimiento explícito. Puedes conectar tu primer VPS gratis o escanear la superficie externa con nuestro analizador perimetral.