SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-24

Docker: enrutar contenedores por una VPN con Gluetun

Al usar network_mode: service:gluetun, los puertos publicados desaparecen y aparece un error de Docker. Vea el compose correcto y cómo exponerlos.

Por qué desaparecen los puertos al enrutar contenedores Docker mediante una VPN

Para enrutar contenedores Docker mediante una VPN, se asigna el túnel a un contenedor y después se conectan los demás a su espacio de nombres de red con network_mode: "service:gluetun". Esa conexión es la parte que suele causar confusión. El contenedor conectado ya no tiene una red propia, por lo que también pierde los puertos publicados y el nombre de servicio de Docker. Publique los puertos en el contenedor de la VPN. Los demás contenedores podrán acceder a la aplicación mediante el nombre del contenedor de la VPN.

Si deja un bloque ports: en el contenedor conectado, Docker rechazará su creación:

Error response from daemon: conflicting options: port publishing and the container type network mode

La herramienta utilizada aquí es Gluetun, un contenedor que se conecta a un proveedor comercial de VPN (red privada virtual) mediante WireGuard u OpenVPN y lleva su propio firewall. La versión v3.41.3 es la actual en agosto de 2026. Los ejemplos usan Mullvad con WireGuard, por lo que necesita una cuenta y una clave de su proveedor. Si prefiere terminar el túnel en hardware propio, crear su propio servidor WireGuard en un VPS configura el otro extremo, y wg-easy en Docker lo integra en una interfaz web.

Qué hace realmente network_mode: "service:gluetun"

Normalmente, cada contenedor de Docker obtiene su propio espacio de nombres de red: sus propias interfaces, tabla de enrutamiento, reglas de firewall y sockets en escucha. El modo service: omite ese paso e inicia el contenedor dentro del espacio de nombres de gluetun. Un solo espacio de nombres implica una sola dirección IP, y eso cambia seis aspectos.

  • La aplicación no tiene una dirección propia. Usa la dirección de gluetun.
  • La aplicación no está conectada a ninguna red de Docker, por lo que su nombre de servicio nunca se registra ni se resuelve. Los demás contenedores deben usar gluetun.
  • Los contenedores que están dentro del espacio de nombres se comunican entre sí mediante localhost.
  • Dos contenedores del mismo espacio de nombres no pueden escuchar en el mismo puerto. La documentación de Gluetun lo indica claramente: no existe ninguna solución alternativa.
  • Las capacidades pertenecen a un contenedor, no a un espacio de nombres. Gluetun tiene NET_ADMIN y /dev/net/tun porque crea la interfaz del túnel. El contenedor conectado no las hereda.
  • Compose rechaza cualquier archivo en el que un servicio establezca a la vez network_mode y networks. Conecte gluetun a sus redes y la aplicación utilizará esas redes.

Reiniciar gluetun desconecta todo lo que está conectado a él. Este comportamiento está documentado y es la razón por la que gluetun reinicia el proceso VPN dentro del contenedor en lugar de salir cuando falla la conexión. Después de reiniciar o recrear gluetun manualmente, reinicie los contenedores conectados a él.

El archivo de Compose que funciona

services:
  gluetun:
    image: qmcgaw/gluetun:v3
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      - VPN_SERVICE_PROVIDER=mullvad
      - VPN_TYPE=wireguard
      - SERVER_CITIES=Amsterdam
      - TZ=Europe/Amsterdam
    env_file:
      - ./gluetun.env
    volumes:
      - ./gluetun:/gluetun
    ports:
      - 127.0.0.1:8080:8080/tcp
    restart: unless-stopped

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    network_mode: "service:gluetun"
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Amsterdam
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent:/config
      - ./downloads:/downloads
    depends_on:
      gluetun:
        condition: service_healthy
    restart: unless-stopped

La etiqueta :v3 es la versión estable más reciente de la serie v3. La etiqueta :latest apunta al último commit de la rama master, que corresponde al estado de desarrollo. Por eso, fije :v3 en una máquina que no quiera depurar un martes.

WEBUI_PORT=8080 debe coincidir con el puerto publicado, porque qBittorrent se enlaza dentro del namespace de gluetun y la regla de publicación envía el tráfico del host al puerto 8080 en ese namespace. Si cambia un número y no el otro, el puerto no responderá. 127.0.0.1:8080:8080 mantiene la interfaz web en la dirección de loopback del host. Un 8080:8080 sin dirección publica el puerto en todas las interfaces y crea su propia regla de firewall. Así es como los puertos publicados por Docker pasan directamente por alto ufw.

Inícielo y compruébelo en este orden:

docker compose up -d
docker compose ps
docker compose logs gluetun | tail -30

docker compose ps debería mostrar gluetun como healthy y qbittorrent como running. Después, confirme la dirección de salida desde dentro del namespace. Esta comprobación determina todo lo demás:

docker run --rm --network=container:gluetun alpine:3.22 sh -c "apk add wget && wget -qO- https://ipinfo.io"

El campo ip de ese JSON debería contener la dirección de su proveedor de VPN. Si contiene la dirección de su propio servidor, la aplicación no está dentro del túnel y nada de lo siguiente funcionará como se describe.

Mantenga las claves fuera del archivo compose

gluetun.env contiene las credenciales y se mantiene fuera de git:

WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
WIREGUARD_ADDRESSES=10.64.222.21/32

Ambos valores proceden de un archivo de configuración de WireGuard que genera en el área de cuenta de su proveedor. Establezca el archivo con el modo 600. Sea preciso sobre lo que consigue: la clave queda fuera del repositorio, pero docker inspect gluetun sigue mostrando todas las variables de entorno a cualquiera que pueda acceder al socket de Docker. Archivos de entorno y secretos en Docker Compose explica opciones más seguras.

Cómo se comunica un contenedor externo al túnel con uno interno

La comunicación funciona en ambas direcciones y cada una usa un nombre distinto. Los dos contenedores necesitan una red Docker compartida. Esa red debe ser la red de gluetun, porque el contenedor conectado no tiene una red propia. Cómo se conectan las redes de Docker Compose explica los valores predeterminados.

Para comunicarse desde fuera hacia dentro, use el nombre de gluetun y el puerto en el que escucha la aplicación. Un contenedor de proxy inverso llega a la interfaz web de qBittorrent mediante gluetun:8080. No se necesita ninguna entrada ports:, porque el tráfico entre contenedores permanece en la red Docker y nunca pasa por un puerto del host.

Para comunicarse desde dentro hacia fuera, use el nombre del servicio del otro contenedor, por ejemplo postgres:5432. Gluetun resuelve los nombres de otros contenedores desde dentro de su espacio de nombres desde v3.41. Fije esa versión o una posterior si un nombre no se puede resolver.

El firewall de Gluetun decide quién puede abrir una conexión hacia él. Se permite el tráfico procedente de la propia red Docker de gluetun. Un cliente de otra subred, un portátil de la LAN o un contenedor de otra red bridge se descarta hasta que se indique esa subred:

FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24

El significado documentado es exacto: subredes separadas por comas a las que Gluetun y los contenedores que comparten su pila de red tienen permitido acceder.

Las conexiones entrantes desde Internet son un problema independiente. Los pares de un cliente torrent llegan desde el lado de la VPN, por lo que publicar el puerto 6881 en el host no les sirve. Necesita un puerto reenviado por su proveedor y debe incluir ese puerto en FIREWALL_VPN_INPUT_PORTS, que permite puertos desde el lado del servidor VPN. Esta es la parte que la mayoría de las pilas multimedia creadas con Docker Compose dejan sin configurar.

El interruptor de emergencia: qué ocurre cuando el túnel se cae

Este patrón justifica su complejidad cuando se produce un fallo. El contenedor conectado no tiene una segunda ruta. Su única salida de la máquina es el namespace que comparte, por lo que, cuando el túnel está caído, no existe una ruta alternativa. El firewall de Gluetun aplica la misma regla desde el otro lado: el tráfico saliente pasa por el túnel o llega al endpoint del servidor VPN, y todo lo demás se descarta. No existe ningún intervalo en el que los paquetes puedan salir por la interfaz normal mientras el cliente se vuelve a conectar.

Gluetun supervisa su propia conexión. Cada minuto envía un eco ICMP (un ping) a las direcciones de HEALTH_ICMP_TARGET_IPS, que de forma predeterminada son 1.1.1.1,8.8.8.8. Cada cinco minutos establece una conexión TCP y TLS (transport layer security) completa con HEALTH_TARGET_ADDRESSES, cuyo valor predeterminado es cloudflare.com:443,github.com:443. Cuando estas comprobaciones fallan, reinicia la VPN dentro del contenedor y lo registra:

WARN [vpn] restarting VPN because it failed to pass the healthcheck: periodic check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout

Revise los registros del contenedor conectado teniendo en cuenta ese orden. Las líneas como connection refused, operation not permitted y i/o timeout dentro de la aplicación son consecuencias de un túnel caído, no las causas. La documentación de Gluetun lo indica explícitamente, porque es habitual informar de la consecuencia y buscar su causa durante horas.

HEALTH_RESTART_VPN=on es el valor predeterminado y debe mantenerse activado. Desactívelo sólo mientras depura un fallo concreto, porque, si está desactivado, un túnel caído permanece caído.

Orden de inicio: impedir que la pila arranque antes de que el túnel esté activo

La imagen incluye un healthcheck de Docker:

HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck

Ese comando ejecuta una segunda copia de gluetun de corta duración, que consulta el servidor de estado de la instancia en ejecución en http://127.0.0.1:9999/. Un túnel operativo responde 200 OK. Uno averiado responde 500 Internal server error con una cadena de error, y el contenedor se marca como no saludable después de un único fallo.

condition: service_healthy es lo que espera a que eso ocurra. El valor simple depends_on: [gluetun] sólo espera a que se inicie el contenedor. Esto sucede varios segundos antes de que se complete el handshake, por lo que la aplicación arranca con la red inoperativa y a menudo abandona su primer intento de conexión. Comprobaciones de estado en Docker Compose explica la sintaxis y los campos de temporización.

Hay un límite que suele causar problemas. Compose evalúa esa condición una sola vez, cuando crea el contenedor. No detiene ni reinicia la aplicación posteriormente si gluetun pasa a estar no saludable. La recuperación automática interna de gluetun cubre ese caso. Por eso reinicia el proceso VPN en lugar del contenedor.

Compruebe si hay una fuga de DNS antes de confiar en la configuración

DNS (sistema de nombres de dominio) es la fuga que persiste aunque el túnel esté configurado correctamente. Gluetun ejecuta su propio resolvedor dentro del espacio de nombres y reenvía las consultas mediante DoT (DNS sobre TLS) a Cloudflare de forma predeterminada: DNS_UPSTREAM_RESOLVER_TYPE=dot y DNS_UPSTREAM_RESOLVERS=cloudflare. No modifique ninguno de los dos valores para que las consultas se cifren y viajen por el túnel.

La opción que rompe este comportamiento es DNS_UPSTREAM_PLAIN_ADDRESSES. Se suele activar cuando un nombre no se resuelve y se quiere que responda el router o el resolvedor del proveedor. La documentación de Gluetun indica claramente el efecto: todo el tráfico DNS no pasará por el túnel VPN y se filtrará fuera de él. El tráfico seguirá siendo privado. La lista de nombres de host no. La misma configuración incorrecta en WireGuard se explica en DNS que deja de resolverse a través de un túnel WireGuard.

Para probarlo, configure HTTPPROXY=on en gluetun y publique 8888:8888/tcp. Después, dirija un navegador a ese proxy y cargue una prueba de fugas de DNS. El resultado debe identificar a su proveedor o a Cloudflare, nunca al router doméstico. La documentación de Gluetun también advierte de que algunas pruebas de fugas muestran resultados inusuales, porque el resolvedor del espacio de nombres es un intermediario local de caché y no el servidor que responde finalmente. Considere una ubicación geográfica incorrecta o el resolvedor de su propio ISP como la señal real.

Añadir Tailscale junto al contenedor sidecar de la VPN y cuál tiene prioridad

Tailscale es una red superpuesta basada en WireGuard para acceder a sus propios equipos. Algunas personas la ejecutan junto a una VPN de proveedor para mantener una vía de administración hacia la pila. Normalmente no interfieren entre sí, por un motivo que conviene entender. La documentación de Tailscale establece el comportamiento predeterminado: actúa como una red superpuesta, sólo enruta tráfico entre dispositivos que ejecutan Tailscale y no modifica el tráfico de Internet público.

Por tanto, la respuesta depende de un ajuste.

  • Tailscale en su propio contenedor, con la configuración predeterminada: nunca ve el tráfico saliente de la aplicación. Gluetun transporta todo ese tráfico. Tailscale accede a la aplicación mediante gluetun:8080, igual que cualquier otro contenedor externo.
  • Tailscale conectado al namespace de gluetun mediante network_mode: "service:gluetun": necesita su propio cap_add de net_admin y net_raw, porque las capabilities no se comparten con el namespace. En el modo de red userspace predeterminado, TS_USERSPACE está activado. tailscaled no crea ninguna interfaz y funciona como proxy SOCKS5 o HTTP, por lo que no puede modificar el enrutamiento. Gluetun sigue transportando todo el tráfico.
  • Lo mismo, con TS_USERSPACE=false: tailscaled crea un dispositivo de túnel e instala rutas, pero sólo para el rango de tailnet 100.64.0.0/10 y para las rutas de subred que anuncie mediante TS_ROUTES. El tráfico público sigue saliendo a través de gluetun.
  • Cualquiera de las opciones anteriores con un exit node seleccionado, sudo tailscale set --exit-node=<exit-node-ip>: Tailscale reclama la ruta predeterminada y tiene prioridad. No lo combine con gluetun. Una ruta predeterminada, un solo responsable.

Si esas rutas anunciadas son el objetivo y quiere que se pueda acceder a toda la red privada situada detrás del equipo, en lugar de sólo al propio equipo, ejecutar un router de subred de Tailscale en un VPS explica la aprobación de rutas, el reenvío IP y el flag del lado del cliente que TS_ROUTES no configura por sí solo.

Si Tailscale se utiliza para ofrecer una URL de administración en lugar de una ruta, tailscale serve y tailscale funnel colocan HTTPS delante de gluetun:8080 para su tailnet. Sólo funnel lo expone a Internet público.

Un efecto secundario resulta visible cuando Tailscale se ejecuta dentro del túnel. Sus pares ven la dirección del proveedor de VPN, por lo que es normal que recurra a relays con más frecuencia. tailscale status muestra relay "..." junto a un par en lugar de direct cuando ocurre esto. La conexión funciona, pero es más lenta. Si lo único que necesita realmente es la red superpuesta, la diferencia entre WireGuard sin más y Tailscale es un mejor punto de partida.

Qué falla y qué mensaje verá

Docker rechaza la creación del contenedor de la aplicación. Error response from daemon: conflicting options: port publishing and the container type network mode significa que todavía hay un bloque ports: asociado al servicio. Muévalo a gluetun.

Compose rechaza todo el archivo. Un servicio no puede establecer network_mode y networks al mismo tiempo. Configure las redes en gluetun.

Otro contenedor no puede resolver la aplicación. curl: (6) Could not resolve host: qbittorrent es el comportamiento esperado, porque el contenedor asociado no se unió a ninguna red ni registró ningún nombre. Use gluetun y el puerto.

El segundo contenedor asociado no arranca. Dos procesos del mismo espacio de nombres no pueden enlazar el mismo puerto. El proceso que pierde informa de que la dirección ya está en uso. Cambie el puerto interno de la aplicación o ejecute otro gluetun.

La aplicación se queda sin red después de modificar gluetun. Reiniciar o recrear gluetun interrumpe la conectividad de todos los contenedores asociados. Reinicie esos contenedores.

Las páginas pequeñas cargan y las grandes se quedan bloqueadas. El problema es la MTU (unidad máxima de transmisión). El túnel añade sobrecarga y algún punto de la ruta descarta los paquetes demasiado grandes sin devolver un error. Reduzca WIREGUARD_MTU, pruebe 1400 y después 1320.

Gluetun nunca alcanza el estado saludable. La comprobación de inicio indica las primeras causas que debe revisar: WARN [vpn] restarting VPN because it failed to pass the healthcheck: startup check: dialing: dial tcp4: lookup cloudflare.com: i/o timeout. Compruebe si la clave ha caducado. Después, compruebe si la lista de servidores está obsoleta y si el firewall del host bloquea el tráfico UDP saliente.

FAQ

¿Por qué dejaron de funcionar los puertos publicados de mi contenedor detrás de Gluetun?

Porque network_mode: "service:gluetun" coloca el contenedor dentro del espacio de nombres de red de gluetun, y un espacio de nombres tiene una dirección IP y un conjunto de puertos de escucha. La aplicación sigue escuchando, pero la regla de publicación debe estar en el contenedor propietario del espacio de nombres. Traslade la lista ports: al servicio gluetun. Si la dejó en el servicio asociado, Docker ni siquiera la creará: Error response from daemon: conflicting options: port publishing and the container type network mode.

¿Cómo accedo desde un contenedor externo a un contenedor dentro del túnel VPN?

Use el nombre del servicio de gluetun y el puerto en el que escucha la aplicación, por ejemplo gluetun:8080. El contenedor asociado no pertenece a ninguna red de Docker propia, por lo que su nombre nunca se resuelve. No es necesario publicar puertos para el tráfico entre contenedores. En sentido contrario, un contenedor dentro del espacio de nombres puede acceder a un contenedor externo mediante su nombre de servicio, como postgres:5432, en Gluetun v3.41 y versiones posteriores. Un cliente de otra subred, como un portátil de su LAN, es bloqueado por el firewall de gluetun hasta que añada esa subred a FIREWALL_OUTBOUND_SUBNETS.

¿Gluetun funciona como un interruptor de desconexión cuando se cae la VPN?

Sí, por dos motivos a la vez. El contenedor asociado no tiene ninguna ruta aparte de la del espacio de nombres compartido, por lo que un túnel caído lo deja sin una ruta para salir del equipo. El firewall de Gluetun también permite el tráfico saliente sólo a través del túnel y hacia el endpoint del servidor VPN. Gluetun reinicia entonces la VPN internamente y registra WARN [vpn] restarting VPN because it failed to pass the healthcheck, en lugar de salir, porque todos los contenedores asociados pierden la red cuando gluetun se reinicia.

Tailscale y Gluetun en la misma pila: ¿cuál transporta el tráfico saliente?

Gluetun, en todas las configuraciones salvo una. De forma predeterminada, Tailscale sólo enruta el tráfico entre dispositivos de su tailnet y deja intacto el tráfico público. En el modo de espacio de usuario predeterminado de la imagen del contenedor, no crea ninguna interfaz, por lo que no puede afectar al enrutamiento. Con TS_USERSPACE=false instala rutas sólo para 100.64.0.0/10 y las subredes anunciadas. La excepción es un nodo de salida: sudo tailscale set --exit-node=<exit-node-ip> convierte a Tailscale en la ruta predeterminada, y entonces prevalece. Elija un solo producto para administrar la ruta predeterminada en lugar de apilar ambos.