Problema
Al montar un servidor de medios con Jellyfin dentro de un contenedor Docker, muchos administradores de homelab quieren que el tráfico pase por Tailscale para disponer de una red privada sin abrir puertos públicos. En la práctica, el proceso suele tropezar con dos obstáculos recurrentes:
- Conflicto de puertos: Docker reclama puertos que ya están reservados por servicios del host (por ejemplo, 443 para HTTPS).
- Resolución DNS interna: Cuando se asigna un nombre de dominio a la IP local del servidor, la resolución a través de Tailscale puede quedar atrapada en una entrada estática que apunta a la IP de la LAN, impidiendo que el contenedor se comunique correctamente durante la construcción o el arranque.
El resultado típico es un error al intentar compilar o iniciar la imagen de Jellyfin, con mensajes que indican que el puerto 443 está “restricted” o que la conexión a la red Tailscale falla. El problema no es exclusivo de Jellyfin; cualquier servicio que requiera puertos estándar y dependa de DNS interno sufre el mismo patrón.
Causa
1. Puertos reservados por el host
Docker, por defecto, necesita permisos de root para enlazar puertos por debajo del 1024. Si el usuario que ejecuta Docker no pertenece al grupo docker o no tiene privilegios de CAP_NET_BIND_SERVICE, el intento de publicar -p 443:443 falla. En muchos sistemas Debian‑based, la solución rápida es añadir el usuario al grupo docker o usar usermod -aG docker $USER.
2. Entrada DNS estática que sobrescribe la resolución de Tailscale
Tailscale crea su propio DNS interno (100.x.x.x) y, al mismo tiempo, permite mapear dominios personalizados a esa IP. Si previamente se creó una entrada en /etc/hosts o en el DNS local que apunta el dominio (por ejemplo, media.mydomain.com) a la IP de la LAN (192.168.1.10), cualquier petición dentro del contenedor seguirá esa ruta, ignorando la red Tailscale. El contenedor, al intentar descargar capas o resolver nombres externos, se queda atrapado.
3. Falta de configuración de red en Docker para usar la interfaz Tailscale
Docker crea una red bridge aislada que no incluye la interfaz tailscale0. Sin una conexión explícita, los contenedores no pueden alcanzar la red Tailscale, lo que provoca fallos al intentar comunicarse con servicios externos o al exponer puertos a través de la VPN.
Solución
Paso 1 – Garantizar permisos de puerto bajo 1024
sudo usermod -aG docker $(whoami)
newgrp docker
Reiniciar la sesión o el daemon Docker para que el grupo tenga efecto. Alternativamente, usar setcap en el binario Docker:
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/docker
Paso 2 – Eliminar o sobrescribir entradas DNS locales
Revisa /etc/hosts y cualquier zona DNS local (por ejemplo, dnsmasq). Busca líneas como:
192.168.1.10 media.mydomain.com
Elimínalas o comenta la línea. Si necesitas mantener la resolución para la LAN, crea una regla específica en Tailscale:
tailscale set --advertise-routes=192.168.1.0/24
tailscale set --accept-dns=true
tailscale set --hostname=media
Luego, en la consola de Tailscale, asigna el dominio a la IP de la red Tailscale (ejemplo 100.101.102.103). De esta forma, cualquier contenedor que resuelva media.mydomain.com obtendrá la IP de Tailscale y no la de la LAN.
Paso 3 – Conectar Docker a la interfaz Tailscale
Crea una red Docker que use la interfaz tailscale0 como puente:
docker network create \
--driver=bridge \
--subnet=100.101.102.0/24 \
--gateway=100.101.102.1 \
tsnet
Al lanzar el contenedor Jellyfin, enlázalo a esa red:
docker run -d \
--name=jellyfin \
--network=tsnet \
-p 443:443 \
-v /srv/jellyfin/config:/config \
-v /srv/jellyfin/cache:/cache \
-v /media:/media \
jellyfin/jellyfin:latest
Con la red tsnet, el contenedor hereda la ruta a tailscale0 y puede comunicarse con otros nodos Tailscale sin interferir con la tabla de rutas del host.
Paso 4 – Configurar nginx como reverse proxy sobre Tailscale
Instala nginx en el host (no dentro de Docker) y apunta al contenedor usando la IP interna de la red tsnet:
server {
listen 443 ssl;
server_name media.mydomain.com;
ssl_certificate /etc/letsencrypt/live/media.mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/media.mydomain.com/privkey.pem;
location / {
proxy_pass http://100.101.102.2:8096;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Reemplaza 100.101.102.2 por la IP asignada al contenedor (puedes obtenerla con docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' jellyfin). Reinicia nginx y verifica que el dominio responde a través de Tailscale.
Cuándo aplicar esta solución
- Síntomas: errores al iniciar contenedores que exponen puertos 443/80, mensajes de “port is restricted”, o fallos de resolución DNS que apuntan a la IP local en vez de la de Tailscale.
- Entorno: servidores Debian/Ubuntu con Docker y Tailscale instalados, uso de dominios personalizados para acceso externo, y necesidad de mantener la infraestructura dentro de un homelab.
- Exclusiones: si la arquitectura no incluye un reverse proxy o si el dominio se resuelve exclusivamente a través de un proveedor DNS externo sin entradas locales, el paso de eliminación de
/etc/hostspuede no ser necesario.
Código
# 1. Añadir usuario al grupo docker
sudo usermod -aG docker $(whoami)
newgrp docker
# 2. Limpiar entradas DNS locales
sudo sed -i '/media\.mydomain\.com/d' /etc/hosts
# 3. Configurar Tailscale para aceptar DNS y rutas
tailscale set --advertise-routes=192.168.1.0/24
tailscale set --accept-dns=true
# 4. Crear red Docker que use la subred Tailscale
docker network create \
--driver=bridge \
--subnet=100.101.102.0/24 \
--gateway=100.101.102.1 \
tsnet
# 5. Lanzar Jellyfin en la red tsnet
docker run -d \
--name=jellyfin \
--network=tsnet \
-p 443:443 \
-v /srv/jellyfin/config:/config \
-v /srv/jellyfin/cache:/cache \
-v /media:/media \
jellyfin/jellyfin:latest
Verificación
-
Comprobar que el contenedor tiene IP en la subred Tailscale
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' jellyfinLa salida debe pertenecer a
100.101.102.0/24. -
Probar conectividad desde otro nodo Tailscale
curl -k https://media.mydomain.comDebería devolver la página de inicio de Jellyfin sin errores de certificado.
-
Validar que nginx está escuchando en 443
sudo ss -tlnp | grep ':443'La línea debe mostrar
nginxy la dirección0.0.0.0:443. -
Revisar logs de Docker y nginx para asegurarse de que no aparecen errores de “port already in use” ni de DNS.
Notas adicionales
- Persistencia de DNS en Tailscale: después de eliminar la entrada en
/etc/hosts, puede tardar unos segundos en propagarse la nueva resolución a través del daemon de Tailscale. Un reinicio detailscaled(sudo systemctl restart tailscaled) acelera el proceso. - Firewall del host: si usas
ufwoiptables, abre explícitamente el puerto 443 en la interfaztailscale0para evitar bloqueos inesperados. - Actualizaciones de Jellyfin: al actualizar la imagen, verifica que la IP del contenedor no cambie; si lo hace, actualiza la directiva
proxy_passen nginx. - Backup de volúmenes: los volúmenes
/srv/jellyfin/configy/srv/jellyfin/cachepueden ser respaldados condocker run --rm -v /srv/jellyfin/config:/src -v $(pwd):/backup alpine tar czf /backup/jellyfin-config.tar.gz -C /src .para mantener la configuración entre reinstalaciones.