Gluetun: acceder al host y a otros contenedores
Si un contenedor usa la red de Gluetun, no tiene interfaces propias: publica sus puertos en Gluetun y permite solo las subredes necesarias fuera del túnel.
Qué ocurre cuando un contenedor se une a la red de gluetun
Un contenedor que establece network_mode: service:gluetun no tiene interfaces de red propias. Se une al espacio de nombres de red de gluetun, por lo que la publicación de puertos y las reglas del firewall dejan de ser propiedades de ese contenedor y pasan a ser propiedades del servicio gluetun. Todas las respuestas siguientes se derivan de ese hecho.
Un espacio de nombres de red es una copia privada de la pila de red del kernel. Tiene sus propias interfaces, tabla de enrutamiento, reglas del firewall y sockets en escucha. Docker asigna uno a cada contenedor de forma predeterminada. Cuando escribe network_mode: service:gluetun, Docker omite ese paso y coloca el contenedor nuevo dentro del espacio de nombres que gluetun ya posee. El contenedor conserva su propio sistema de archivos y su propio archivo /etc/hosts. Este último será importante más adelante.
Puede verlo directamente.
docker inspect -f '{{.HostConfig.NetworkMode}}' qbittorrentEsto muestra container: seguido del ID del contenedor gluetun, mientras que un contenedor normal mostraría bridge. Esta guía continúa desde enrutar el tráfico de Docker a través de una VPN con gluetun: el túnel funciona y ahora nada puede comunicarse con el contenedor.
Publica el puerto en gluetun, no en la aplicación
Deja un bloque ports: en el servicio que establece network_mode y Docker se niega a crear el contenedor:
Error response from daemon: conflicting options: port publishing and the container type network modeLa razón es directa. Publicar un puerto significa añadir una regla NAT (traducción de direcciones de red) que reenvía un puerto del host al espacio de nombres de red propio de un contenedor, y este contenedor no tiene uno. Mueve la asignación al servicio gluetun. El número de puerto no cambia porque la aplicación sigue escuchando en ese puerto dentro del espacio de nombres compartido.
services:
gluetun:
ports:
- "8080:8080/tcp" # qBittorrent web UI
qbittorrent:
network_mode: "service:gluetun"
# no ports: block hereUn bloque expose: en el servicio dependiente tampoco sirve de nada, y un bloque networks: en ese servicio detiene el proceso: Compose informa de que el servicio declara network_mode y networks, que son mutuamente excluyentes, y se niega a cargar el archivo.
Una consecuencia aparece más adelante. Todos los contenedores del espacio de nombres comparten un único espacio de puertos. Por tanto, dos aplicaciones que usan 8080 de forma predeterminada entran en conflicto, y la segunda que se inicia falla con un error de dirección ya en uso. Cambia una de ellas en su propia configuración, por ejemplo la variable WEBUI_PORT en la imagen LinuxServer qBittorrent, y después publica el nuevo número en gluetun.
¿Cómo se comunican entre sí los contenedores detrás de gluetun?
Dentro del espacio de nombres ya comparten una interfaz de loopback. Un contenedor detrás de gluetun llega a su contenedor hermano en 127.0.0.1:<port> sin utilizar ninguna red de Docker.
Desde fuera del espacio de nombres, el contenedor no tiene nombre. El DNS integrado de Docker resuelve un nombre de servicio en la dirección de ese servicio dentro de una red definida por el usuario, pero este contenedor no tiene ninguna dirección en ninguna red. Por tanto, un contenedor normal como Sonarr no llega al cliente torrent en http://qbittorrent:8080. Llega a él en http://gluetun:8080, porque el socket está escuchando en el espacio de nombres de gluetun, en la dirección de gluetun. Esto sorprende a quienes conocen cómo funcionan las redes y los nombres de servicio de Docker Compose y esperan que se aplique el esquema de nombres habitual. También funciona sin publicar nada en el host, porque ambos contenedores están en la misma red de Compose.
Compruebe el DNS antes de depurar cualquier otra cosa. Gluetun ejecuta su propio resolvedor y reescribe /etc/resolv.conf en su propio contenedor, pero /etc/resolv.conf es un archivo independiente para cada contenedor, por lo que el archivo que escribió gluetun no es el que lee su aplicación.
docker exec qbittorrent cat /etc/resolv.conf¿Cómo accedo a un servicio que se ejecuta en el host de Docker?
Use host.docker.internal. Requiere dos ajustes en dos lugares distintos porque hay dos problemas diferentes.
Primero se configura el nombre. /etc/hosts se aplica a cada contenedor, por lo que la entrada extra_hosts debe estar en el contenedor de la aplicación, no en gluetun.
prowlarr:
network_mode: "service:gluetun"
extra_hosts:
- "host.docker.internal:host-gateway"host-gateway es un valor especial que Docker sustituye por una dirección interna del propio host. En una instalación normal de Docker para Linux, es la dirección del puente docker0, normalmente 172.17.0.1. Confirme la suya con ip -4 addr show docker0 en el VPS. Docker Desktop resuelve este nombre automáticamente. Por eso las guías escritas para un equipo portátil omiten la línea extra_hosts, y el mismo archivo falla después en un servidor.
Después se configura la ruta. Añadir el nombre sólo indica al contenedor qué dirección debe usar. El paquete sigue saliendo por la ruta predeterminada de gluetun, que es el túnel, y el firewall de gluetun lo descarta. El síntoma es una conexión que queda esperando y finalmente agota el tiempo de espera, no una conexión rechazada. Un rechazo significa que el paquete llegó y algo respondió negativamente. Un tiempo de espera agotado significa que nunca llegó.
gluetun:
environment:
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32Después, compruebe que el servicio del host realmente esté escuchando en esa dirección. Un servidor PostgreSQL enlazado sólo a 127.0.0.1 no es accesible desde ningún contenedor, con túnel o sin él, porque 127.0.0.1 dentro del espacio de nombres es el loopback de ese propio espacio de nombres. Enlácelo a 172.17.0.1: así aceptará conexiones de los contenedores sin exponerse en la interfaz pública. Verifíquelo con ss -lntp | grep 5432 en el host.
Qué cambia realmente FIREWALL_OUTBOUND_SUBNETS
La documentación de gluetun lo describe como las subredes separadas por comas a las que gluetun y los contenedores que comparten su pila de red pueden acceder, y señala que implica cambios en el firewall y en el enrutamiento. Ambas partes son importantes. Gluetun añade una ruta para cada subred indicada a través de la puerta de enlace del puente de Docker, por lo que los paquetes destinados a esas direcciones salen por eth0 en lugar de atravesar el túnel. También abre el firewall para esas subredes, porque gluetun descarta de otro modo el tráfico saliente que no está dirigido al servidor VPN.
Escriba el valor sin espacios después de las comas.
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,192.168.1.0/24,100.64.7.9/32Es fácil pasar por alto dos propiedades. Este ajuste se aplica en el ámbito del espacio de nombres, por lo que afecta a todos los contenedores situados detrás de gluetun, no sólo al que tenía en mente. Además, sólo se aplica al tráfico saliente: controla las conexiones que inicia un contenedor. Las conexiones que llegan a un puerto publicado siguen una ruta diferente y no necesitan incluirse aquí.
Acceder a la interfaz web desde un dispositivo de Tailscale
Tailscale asigna a cada máquina una dirección de 100.64.0.0/10, el rango reservado para NAT de nivel de operador. Las dos direcciones requieren configuraciones diferentes.
La entrada es sencilla. Publicar 8080:8080 en gluetun vincula ese puerto a todas las direcciones del host, incluida la interfaz tailscale0. Por tanto, un dispositivo de la red abre http://<machine-name>:8080 y accede al contenedor. Gluetun no interviene en ese recorrido, porque la regla de NAT de Docker se encuentra en el host, fuera del espacio de nombres.
Para que la interfaz sólo sea accesible a través de la tailnet, vincule el puerto publicado a la dirección de Tailscale del host en lugar de vincularlo a todas las direcciones.
ports:
- "100.101.102.103:8080:8080/tcp"Obtenga esa dirección con tailscale ip -4 en el host. En este caso, vincular el puerto proporciona un control más estricto que una regla del firewall, porque el puerto nunca se abre en la interfaz pública. También evita el problema descrito en Publicar puertos de Docker directamente fuera de ufw.
La salida es donde vuelve a intervenir FIREWALL_OUTBOUND_SUBNETS. Si un contenedor tiene que conectarse a un dispositivo de la red, añada la dirección de ese dispositivo y prefiera un /32 por dispositivo en lugar de toda la /10. Los nombres de MagicDNS no se resolverán dentro del contenedor, porque el contenedor no utiliza el resolver del host. Use la dirección numérica 100.x o fíjela con una línea extra_hosts. Lo mismo se aplica cuando ejecuta su propio servidor de control de Tailscale con Headscale.
Un archivo compose completo para el caso habitual
Un cliente de descargas detrás de la VPN, dos interfaces web que responden sólo en tailnet y un contenedor que lee una base de datos PostgreSQL que se ejecuta en el host.
services:
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
- "127.0.0.1:8000:8000/tcp" # gluetun control server, host only
- "100.101.102.103:8080:8080/tcp" # qBittorrent UI, tailnet only
- "100.101.102.103:9696:9696/tcp" # Prowlarr UI, tailnet only
volumes:
- ./gluetun:/gluetun
environment:
- VPN_SERVICE_PROVIDER=mullvad
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
- WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
- SERVER_CITIES=Amsterdam
- FIREWALL_OUTBOUND_SUBNETS=172.17.0.1/32,100.64.7.9/32
- TZ=Europe/Amsterdam
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
network_mode: "service:gluetun"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- WEBUI_PORT=8080
volumes:
- ./qbittorrent:/config
- /srv/downloads:/downloads
depends_on:
gluetun:
condition: service_healthy
restart: unless-stopped
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
network_mode: "service:gluetun"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- PROWLARR__POSTGRES__HOST=host.docker.internal
- PROWLARR__POSTGRES__PORT=5432
- PROWLARR__POSTGRES__USER=prowlarr
- PROWLARR__POSTGRES__PASSWORD=${PROWLARR_DB_PASSWORD}
- PROWLARR__POSTGRES__MAINDB=prowlarr-main
- PROWLARR__POSTGRES__LOGDB=prowlarr-log
volumes:
- ./prowlarr:/config
depends_on:
gluetun:
condition: service_healthy
restart: unless-stoppedLea el archivo para entender el patrón, no los nombres de los productos. Ambas interfaces se publican en gluetun y se enlazan a la dirección tailnet del host, por lo que responden en Tailscale y en ningún otro lugar. Sólo Prowlarr incluye la línea extra_hosts, porque Prowlarr es el contenedor que resuelve los nombres host.docker.internal. FIREWALL_OUTBOUND_SUBNETS define dos direcciones individuales: la dirección del puente Docker del host, para que Prowlarr pueda abrir una conexión con la base de datos, y un equipo de tailnet.
El servidor PostgreSQL está deliberadamente ausente del archivo. Se ejecuta en el VPS como un servicio normal del sistema y escucha en 172.17.0.1:5432. Es la misma estructura que una pila arr en Docker Compose, con la base de datos fuera de Docker.
Mantenga la clave privada de WireGuard fuera del archivo compose. ${WIREGUARD_PRIVATE_KEY} lee el valor de un archivo .env situado junto a él, según el patrón descrito en archivos env y secretos para Docker Compose. La cláusula condition: service_healthy usa el healthcheck que la imagen gluetun ya incluye, por lo que ningún contenedor se inicia hasta que el túnel informa de que está activo. Los healthchecks de Compose explican la estructura general.
Publicar en todas las direcciones en lugar de sólo en tailnet
Elimine el prefijo de dirección y las asociaciones de puertos de 0.0.0.0, que incluye la IP pública del VPS. Hágalo sólo detrás de un firewall que controle y lea antes la nota sobre ufw.
ports:
- "8080:8080/tcp"Confirme que el túnel sigue transportando tráfico
Ejecute la misma solicitud dos veces: una desde dentro del espacio de nombres y otra desde el host. Después, compare los resultados.
docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org
curl -s https://api.ipify.orgLa primera debe mostrar la dirección de salida de su proveedor de VPN. La segunda debe mostrar la dirección del VPS. Si coinciden, el tráfico del contenedor no está pasando por el túnel. Hasta corregirlo, el resto de esta guía no es aplicable.
La tabla de enrutamiento muestra qué tráfico usa el túnel y cuál no.
docker run --rm --network=container:gluetun alpine:3.22 ip route showLa ruta predeterminada debe apuntar a la interfaz del túnel, tun0. Debajo debe aparecer una ruta por cada entrada de FIREWALL_OUTBOUND_SUBNETS, con destino a la puerta de enlace del puente de Docker. Cualquier otra ruta que salga por eth0 corresponde a tráfico que omite la VPN.
El servidor de control de Gluetun informa de la misma IP pública en el puerto 8000, en /v1/publicip/ip. Las versiones recientes requieren configurar la autenticación para las rutas del servidor de control. Configure esa autenticación antes de depender de este mecanismo.
La fuga que provoca una subred incorrecta
FIREWALL_OUTBOUND_SUBNETS es un agujero que se abre deliberadamente en el firewall, por lo que el tamaño del agujero determina el tamaño del riesgo. Hay cuatro formas de hacerlo demasiado grande:
0.0.0.0/0envía todo el tráfico fuera del túnel. Las dos comprobaciones de IP anteriores detectan este problema en la primera ejecución, porque devolverán la misma dirección.- Un rango más amplio que el objetivo. Abrir
10.0.0.0/8para llegar a un equipo en10.0.1.7también abre todas las direcciones que un par de torrents pueda anunciar dentro de ese rango. Escriba10.0.1.7/32. - Un rango que se solapa con las direcciones del propio túnel. La documentación de gluetun advierte que esto hace que gluetun envíe el tráfico VPN a través del bridge, lo que rompe el reenvío de puertos. Compruebe el valor de
WIREGUARD_ADDRESSESantes de abrir cualquier rango privado. 100.64.0.0/10para Tailscale. Eso abre aproximadamente cuatro millones de direcciones para poder alcanzar a un solo par. Enumere los pares necesarios como entradas/32.
Recuerde que esta configuración cubre todo el namespace. Abrir una subred para que un indexer pueda llegar a un servicio del host abre la misma subred para el cliente de torrents que comparte ese namespace. Vuelva a ejecutar la comprobación de IP pública después de cada cambio en esta variable, porque es la única prueba que muestra si el cambio produjo el resultado previsto.
Qué se rompe al reiniciar gluetun
Gluetun controla el espacio de nombres, por lo que el ciclo de vida de gluetun es el ciclo de vida del espacio de nombres. Si inicia un contenedor dependiente mientras gluetun está detenido, el inicio falla de inmediato:
Error response from daemon: cannot join network of a non running containerReiniciar gluetun en su lugar provoca un fallo menos evidente. Los contenedores dependientes siguen ejecutándose mientras el espacio de nombres al que están conectados se reconstruye por debajo de ellos, de modo que docker ps informa que todo está operativo, pero ningún servicio responde. Después de cambiar el servicio gluetun, vuelva a crear todo el grupo en lugar de reiniciar una sola parte.
docker compose up -d --force-recreateLo mismo se aplica a las actualizaciones de imágenes. Si descarga una imagen nueva de gluetun y vuelve a crear sólo ese servicio, los demás contenedores quedan apuntando a un espacio de nombres que ya no existe.
FAQ
¿Por qué Docker muestra "port publishing and the container type network mode"?
Porque todavía hay un bloque ports: en un servicio que también define network_mode: service:gluetun. Publicar un puerto añade una regla NAT que reenvía un puerto del host al espacio de nombres de red propio de un contenedor, pero un contenedor en este modo no tiene uno. Elimine el bloque ports: de ese servicio y añada el mismo mapeo al servicio gluetun. El número de puerto no cambia, porque la aplicación sigue escuchando en él dentro del espacio de nombres compartido.
¿Cómo acceden otros contenedores a un servicio situado detrás de gluetun?
Los contenedores que están dentro del mismo espacio de nombres se conectan entre sí mediante 127.0.0.1. Los contenedores que están fuera de él usan el nombre del servicio gluetun, por lo que http://gluetun:8080 funciona, mientras que http://qbittorrent:8080 no. El contenedor de la aplicación no tiene ninguna dirección en una red Docker, por lo que el servidor DNS integrado no tiene nada que resolver para su nombre. No es necesario publicar ningún puerto para esto, siempre que ambos contenedores compartan una red de Compose.
¿Qué debo incluir en FIREWALL_OUTBOUND_SUBNETS?
Sólo las direcciones con las que un contenedor situado detrás de gluetun debe iniciar una conexión, especificadas de la forma más restringida posible. Una sola máquina es un /32. Las dos entradas habituales son el host Docker en 172.17.0.1/32 y un /32 por cada nodo de Tailscale al que se conecte. No añada nunca 0.0.0.0/0 ni un rango que se solape con las direcciones del túnel propio de la VPN. Las conexiones entrantes a un puerto publicado no necesitan ninguna entrada aquí.
¿Por qué el contenedor no puede resolver mis nombres MagicDNS de Tailscale?
MagicDNS funciona configurando el resolvedor del host para que use el servidor DNS de Tailscale, pero el contenedor no usa el resolvedor del host. Usa lo que indique su propio /etc/resolv.conf, que detrás de gluetun es la configuración DNS de gluetun. Confírmelo con docker exec <container> cat /etc/resolv.conf. Use la dirección numérica 100.x del nodo o fije el nombre mediante una entrada extra_hosts en ese contenedor.
¿Cómo confirmo que el tráfico sigue pasando por la VPN?
Ejecute una petición desde dentro del espacio de nombres y la misma petición desde el host. Después, compare las respuestas. docker run --rm --network=container:gluetun curlimages/curl:latest -s https://api.ipify.org debería devolver la dirección de salida de su proveedor de VPN, mientras que curl -s https://api.ipify.org en el VPS devuelve la dirección del VPS. Dos respuestas idénticas indican que el túnel no transporta el tráfico del contenedor. Repita esta comprobación después de cada cambio en FIREWALL_OUTBOUND_SUBNETS.