Conectar un contenedor Docker al SSH del host con UFW

·5 min de lectura·

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.internal resuelva 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é

DatoEjemplo del casoQué indica
Nombre del hosthost.docker.internalNombre que el contenedor usa como destino
IP resuelta172.17.0.1Dirección del host a la que se intenta conectar
IP del contenedor172.29.0.4Origen IPv4 de la conexión en la interfaz de salida
Gateway de la red172.29.0.1Puerta de enlace de la red Docker del contenedor
Subred autorizada en UFW172.29.0.0/16Rango 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.internal ayuda a localizar el host, pero no revela la IP de origen con la que el contenedor llega a UFW.
  • docker network inspect muestra la subred de cada red Docker; ip route dentro 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.

Contenedores Docker

📚 Fuentes consultadas

  1. Conectividad personalizada con extra_hosts y host-gateway - Docker Docs
  2. Configurar la dirección de host-gateway - Docker Docs
  3. Sintaxis de reglas, comentarios y tráfico reenviado de UFW - Ubuntu Manpage

¿Te ha resultado útil?

Compártelo con tu red de contactos profesionales.

Comentarios (0)

Sé el primero en comentar.