En una aplicación desplegada con Docker necesité conectarme por SSH al propio servidor para ejecutar una operación de mantenimiento. sshd estaba escuchando en todas las interfaces y el contenedor resolvía host.docker.internal, pero la conexión al puerto TCP expiraba.
La causa no era el servicio SSH: la regla de UFW autorizaba una subred distinta de la que usaba el contenedor. Esta guía recoge el diagnóstico y una forma de conceder acceso solo desde la red Docker correspondiente.
Idea clave: que
host.docker.internalresuelva a una IP del host no implica que UFW permita la conexión. Hay que distinguir la IP destino del host de la IP de origen del contenedor.
El síntoma: el host responde al ping, pero SSH expira
La prueba inicial desde el contenedor fue:
nc -vz -w 3 host.docker.internal 22
El nombre resolvía a 172.17.0.1, y ping recibía respuesta, pero nc expiraba. Esto confirmaba que el contenedor alcanzaba esa dirección; no confirmaba que el puerto TCP estuviera permitido ni que el destino fuera el correcto para la topología de red.
En el servidor, ss mostraba sshd escuchando en todas las interfaces IPv4 e IPv6:
0.0.0.0:22
[::]:22
Por tanto, el siguiente paso era revisar el firewall y las rutas del contenedor.
Revisar la regla de UFW
UFW estaba activo con una política de entrada deny. El puerto 22 solo se permitía desde una IP concreta. Se añadió una regla para 172.17.0.0/16, que es la subred habitual de la red bridge predeterminada en esa instalación:
sudo ufw allow from 172.17.0.0/16 to any port 22 proto tcp comment 'SSH desde Docker bridge'
La conexión seguía expirando. La regla existía, pero el contador de paquetes no aumentaba. Eso era una pista de que el tráfico no llegaba con una IP de origen dentro de 172.17.0.0/16.
Comprobar las redes reales del contenedor
docker network inspect bridge mostró que la red bridge predeterminada usaba 172.17.0.0/16, pero también indicó que no tenía contenedores conectados. El contenedor de la aplicación estaba en redes definidas por Docker Compose.
Dentro del contenedor se instaló iproute2 para consultar las rutas:
apt update
apt install -y iproute2
ip route
La salida relevante fue:
default via 172.29.0.1 dev eth0
172.26.0.0/16 dev eth1 proto kernel scope link src 172.26.0.12
172.29.0.0/16 dev eth0 proto kernel scope link src 172.29.0.4
El contenedor tenía dos interfaces. Para el destino host.docker.internal (172.17.0.1), la ruta por defecto salía por eth0 con origen 172.29.0.4. Por tanto, la regla de UFW debía permitir la subred 172.29.0.0/16, no la bridge predeterminada 172.17.0.0/16.
Autorizar solo la subred correcta
Se reemplazó la regla por una que coincidía con la red de origen efectiva:
sudo ufw delete allow from 172.17.0.0/16 to any port 22 proto tcp
sudo ufw allow from 172.29.0.0/16 to any port 22 proto tcp comment 'SSH desde la red Docker de la aplicación'
Al repetir nc, la conexión tuvo éxito. La diferencia estaba en la subred de origen del contenedor, no en el nombre DNS ni en el puerto de sshd.
Comprueba las reglas aplicadas con:
sudo ufw status numbered
Y vuelve a probar desde el contenedor:
nc -vz -w 3 host.docker.internal 22
Qué dirección mirar y por qué
| Dato | Ejemplo del caso | Qué indica |
|---|---|---|
| Nombre del host | host.docker.internal | Nombre que el contenedor usa como destino |
| IP resuelta | 172.17.0.1 | Dirección del host a la que se intenta conectar |
| IP del contenedor | 172.29.0.4 | Origen IPv4 de la conexión en la interfaz de salida |
| Gateway de la red | 172.29.0.1 | Puerta de enlace de la red Docker del contenedor |
| Subred autorizada en UFW | 172.29.0.0/16 | Rango cuyo tráfico puede llegar al puerto SSH |
host.docker.internal es un nombre cómodo para alcanzar el host, pero en Docker Engine sobre Linux puede ser necesario mapearlo con host-gateway en Compose:
services:
coreapi:
extra_hosts:
- "host.docker.internal:host-gateway"
En Linux, host-gateway resuelve por defecto a la dirección del host asociada con la bridge predeterminada. El contenedor puede estar conectado a una red definida por Compose distinta de esa bridge. Por eso conviene revisar tanto el nombre y destino como la ruta y la IP de origen reales.[1][2]
Aplicar el acceso con el menor alcance necesario
Una regla para 172.29.0.0/16 permite el acceso desde las direcciones de esa red al puerto 22. Si otros servicios usan subredes diferentes, no quedarán cubiertos. Para dar acceso a otra red, inspecciona su subred y añade una regla específica:
docker network inspect nombre_de_la_red
sudo ufw allow from SUBRED_DOCKER to any port 22 proto tcp comment 'SSH desde nombre de la red'
Evita autorizar rangos privados amplios sin comprobarlos. Un rango como 172.16.0.0/12 incluye muchas direcciones que podrían no pertenecer a Docker. Si gestionas varias redes, es más claro permitir las subredes concretas o definir un rango de direcciones Docker dedicado y documentarlo.
La regla anterior controla el acceso entrante al host. Si el destino fuera otro contenedor o una máquina a la que el host enruta tráfico, el flujo sería distinto y habría que revisar las reglas de tráfico reenviado (route) de UFW, además de las reglas de Docker.[3]
Conclusiones
host.docker.internalayuda a localizar el host, pero no revela la IP de origen con la que el contenedor llega a UFW.docker network inspectmuestra la subred de cada red Docker;ip routedentro del contenedor revela interfaces, gateway y origen seleccionado.- La regla debe coincidir con la subred efectiva del contenedor y limitarse al puerto necesario.
- Si la topología cambia, revisa la regla y vuelve a probar la conexión desde el contenedor.
