Problema
En entornos Docker Swarm con un reverse proxy (por ejemplo, Traefik) y un proveedor de identidad (Authelia, Keycloak, etc.) es frecuente que los servicios que implementan OAuth2 necesiten redirigir al usuario a una URL de callback que apunta al host del clúster (ej. auth.example.com).
Los contenedores que forman parte de una overlay network de Swarm no pueden alcanzar directamente la IP del nodo host ni el nombre DNS que resuelve a esa IP. La resolución DNS funciona, pero la petición nunca llega porque Docker aísla la red del contenedor del stack de red del host. El síntoma típico es:
- El navegador muestra la URL de callback (
https://auth.example.com/...) y, al ser redirigida, el contenedor que ejecuta la aplicación (HedgeDoc, Immich, etc.) intenta contactarauth.example.compero recibe “connection refused” o “timeout”. - Añadir una entrada
extra_hostscon la IP del nodo funciona momentáneamente, pero la IP cambia cada vez que el Swarm se reinicia, obligando a actualizar manualmente eldocker‑compose.yml.
Este patrón se repite en cualquier stack donde:
- El flujo OAuth2 depende de un endpoint externo que está detrás del mismo reverse proxy.
- Los contenedores están en una overlay network y no usan
hostnetwork mode. - No se dispone de un DNS interno que apunte a la IP del proxy dentro del clúster.
Causa
Docker Swarm crea redes overlay que son totalmente independientes de la red del host. Cada servicio recibe una IP virtual dentro del rango de la overlay y el tráfico entre contenedores se enruta a través del plano de datos de Swarm. Las razones principales por las que el contenedor no llega al host son:
- Aislamiento de red: por defecto, los contenedores no pueden usar la interfaz de loopback del nodo ni la IP del nodo. La tabla de rutas del contenedor no contiene una ruta hacia la subred del host.
- IP dinámica del nodo: cuando el nodo se reinicia, Docker asigna una nueva IP al ingress network (el que expone los puertos publicados). Las entradas estáticas en
extra_hostsquedan obsoletas. - Falta de descubrimiento interno: el nombre
auth.example.comse resuelve mediante DNS externo a la IP del nodo, pero el contenedor no tiene una ruta de salida que apunte a esa IP, por lo que el paquete se descarta antes de salir del namespace de red. - Política de seguridad: Docker impide que los contenedores accedan a la red del host para evitar que una aplicación comprometida pueda atacar al daemon o a otros procesos del host.
Solución
La solución consiste en exponer el endpoint de OAuth2 dentro de la propia overlay network, de modo que los contenedores lo consuman mediante un nombre de servicio Docker en lugar de la IP del host. Hay tres enfoques probados y reutilizables:
1. Publicar el proxy como servicio interno y usar su nombre DNS
- Declara Traefik (o cualquier otro reverse proxy) como un servicio Docker Swarm con modo
hostoingressy exponlo en la overlay mediante la etiquetatraefik.enable=true. - Añade una regla de router que escuche en un sub‑dominio interno, por ejemplo
auth.internal.local. - En los contenedores que necesiten el callback, configura la URL de redirección a
https://auth.internal.local/callback. - Docker DNS resolverá automáticamente
auth.internal.localal IP virtual del servicio Traefik dentro de la overlay, sin depender de la IP del nodo.
Este método mantiene todo el tráfico dentro del clúster y evita la necesidad de extra_hosts.
2. Utilizar “network‑mode: host” solo para el servicio de autenticación
Si el proveedor de identidad no necesita aislamiento (por ejemplo, Authelia funciona exclusivamente como backend HTTPS), puedes lanzar el contenedor con:
services:
authelia:
image: authelia/authelia
network_mode: host
ports:
- "9091:9091"
Al estar en host network mode, el contenedor comparte la pila de red del nodo y responde directamente a la IP del host. Los demás servicios siguen en la overlay y pueden referenciar auth.example.com sin problemas porque la petición sale del contenedor a la red del host y vuelve a entrar por el proxy.
3. Crear una red “attachable” y usar “extra_hosts” dinámico con Docker‑Compose
Cuando la primera opción no es viable (por ejemplo, se necesita un proxy externo distinto), se puede automatizar la generación de extra_hosts mediante un pequeño script que:
- Obtiene la IP actual del nodo para la red
ingress:docker network inspect ingress -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}' - Genera un archivo
extra_hosts.ymlcon la entrada:extra_hosts: - "auth.example.com:<IP_GATEWAY>" - Incluye ese archivo en el
docker-compose.ymlcon!include extra_hosts.yml.
Al ejecutar docker stack deploy después de cada reinicio del nodo, el script actualiza la IP y el despliegue queda coherente sin intervención manual.
4. Usar un DNS interno (CoreDNS, Consul) que apunte al proxy
Instala un servidor DNS ligero dentro del Swarm (CoreDNS) y crea una zona que resuelva auth.example.com a la IP del servicio Traefik. Configura los contenedores para que usen ese DNS (opción --dns en la definición del servicio). De esta forma, cualquier cambio de IP del nodo se refleja automáticamente en la zona DNS sin tocar los manifiestos.
Cuándo aplicar esta solución
| Señal / Síntoma | Enfoque recomendado |
|---|---|
| Necesitas que todos los servicios usen la misma URL de callback y la URL está bajo tu dominio público. | Opción 1 (proxy interno con sub‑dominio) |
| El proveedor de identidad no soporta TLS interno y depende del proxy para HTTPS. | Opción 1 o Opción 3 (automatizar extra_hosts) |
| El contenedor de autenticación es el único que necesita acceso directo al host y no hay conflicto de puertos. | Opción 2 (host network mode) |
| Prefieres mantener la arquitectura “cero cambios en la aplicación” y ya dispones de un DNS interno. | Opción 4 (CoreDNS/Consul) |
| No puedes modificar la configuración del reverse proxy (p. ej. SaaS) | Opción 3 (script de extra_hosts) |
No apliques la solución si:
- Tu política de seguridad prohíbe
hostnetwork mode. - El proxy externo está fuera del control del clúster y no puedes crear un sub‑dominio interno.
- No deseas introducir un DNS adicional por complejidad operativa.
Código
# 1. Crear una overlay network attachable (si aún no existe)
docker network create \
--driver overlay \
--attachable \
swarm_net
# 2. Deploy de Traefik como servicio interno con nombre DNS "traefik"
docker service create \
--name traefik \
--network swarm_net \
--publish 80:80 \
--publish 443:443 \
--label traefik.enable=true \
--label traefik.http.routers.auth.rule="Host(`auth.internal.local`)" \
--label traefik.http.services.auth.loadbalancer.server.port=9091 \
traefik:v2.10
# 3. Deploy de Authelia (puede usar host mode o overlay)
docker service create \
--name authelia \
--network swarm_net \
--publish 9091:9091 \
--label traefik.enable=true \
--label traefik.http.routers.authelia.rule="Host(`auth.internal.local`)" \
--label traefik.http.routers.authelia.entrypoints=websecure \
authelia/authelia:latest
En los servicios que consumen OAuth2 (HedgeDoc, Immich, etc.) basta con apuntar la URL de callback a https://auth.internal.local/callback. Docker DNS resolverá auth.internal.local al IP virtual de Traefik dentro de swarm_net.
## Verificación
1. **Comprobar resolución DNS interna**
```bash
docker run --rm --network swarm_net alpine nslookup auth.internal.local
Debería devolver una IP del rango de la overlay (ej. 10.0.0.5).
-
Probar la ruta de callback
Desde cualquier contenedor del stack, ejecuta:curl -I https://auth.internal.local/api/verifyDebería responder
200 OKsin timeout. -
Reiniciar un nodo
Detén y enciende el nodo del manager, luego vuelve a lanzardocker service ls. Verifica que la IP deauth.internal.localsigue resolviéndose y que la aplicación OAuth2 completa el flujo sin intervención manual. -
Revisar logs de Traefik
docker service logs traefik --tail 20Busca entradas que confirmen que la petición a
/callbackllegó y fue redirigida correctamente.
Notas adicionales
- Mantén la overlay lo más pequeña posible: demasiadas sub‑redes pueden generar colisiones de IP y dificultar la depuración.
- TLS terminación: si usas Traefik para terminar TLS, asegúrate de que el backend (Authelia) acepte tráfico HTTP interno; de lo contrario, habilita
traefik.http.services.auth.loadbalancer.server.scheme=https. - Persistencia de la configuración de DNS interno: cuando uses CoreDNS o Consul, exporta la zona como ConfigMap o archivo de configuración versionado para que los despliegues sean reproducibles.
- Monitoreo: agrega métricas de Traefik (
/metrics) y de Authelia para detectar rápidamente fallos de callback después de cambios de infraestructura.