Problema

Los entusiastas de los servidores domésticos suelen combinar Plex con servicios de arr (Sonarr, Radarr, etc.) y clientes de Usenet o torrents. En la práctica, aparecen dos grupos de fallos recurrentes:

  1. Plex se cae cada vez que se reinicia Docker – los contenedores que gestionan descargas y organización se ejecutan en la misma máquina que Plex, de modo que cualquier reinicio de Docker desmonta los volúmenes y rompe la biblioteca.
  2. Los hard links no funcionan – cuando las descargas se guardan en un filesystem distinto al de la biblioteca, Sonarr/Radarr copian los archivos en vez de enlazarlos, duplicando temporalmente el uso de disco y saturando el pool ZFS.
  3. Inconsistencias de rutas y permisos – los servicios dentro de Docker esperan rutas absolutas diferentes a las que ve el host, lo que provoca que los archivos no se muevan o que los procesos fallen al iniciar.

El patrón subyacente es una arquitectura monolítica donde Plex, los arr y el cliente de Usenet comparten el mismo entorno de contenedores o VM sin una separación clara del almacenamiento. El resultado es inestabilidad, pérdida de rendimiento y una gestión de permisos engorrosa.

Causa

1. Falta de aislamiento de servicios críticos

Ejecutar Plex dentro de Docker o en la misma VM que los arr hace que cualquier operación de Docker (reinicio, actualización, despliegue) afecte a Plex. Además, la asignación de GPU para transcodificación suele requerir passthrough a nivel de hipervisor; mezclar esto con Docker genera conflictos de dispositivos.

2. Almacenamiento fragmentado

ZFS permite crear datasets con propiedades independientes (compress, dedup, quota). Cuando los downloads se guardan en un dataset distinto al de la biblioteca, el kernel no puede crear hard links entre sistemas de archivos diferentes. Sonarr y Radarr, al no encontrar la capacidad de enlazar, recurren a copiar.

3. Mapeo de rutas inconsistente

Los contenedores Docker usan su propio namespace de filesystem. Si el host monta /mnt/media/tv en /media/tv dentro del contenedor, pero Plex (en LXC) ve /mnt/media/tv, los servicios no comparten la misma ruta y los scripts de movimiento fallan.

4. Permisos predeterminados

Muchas imágenes Docker usan variables PUID/PGID para mapear al usuario del host. Si los directorios de configuración se crean como root antes de lanzar el contenedor, el cambio de UID/GID no tiene efecto y los procesos no pueden escribir.

Solución

Separar Plex y los arr en dos entornos aislados pero con acceso al mismo pool ZFS mediante NFS. La arquitectura recomendada es:

  1. LXC para Plex – un contenedor ligero que monta directamente los datasets ZFS (/mnt/media/*). El LXC tiene acceso directo a la GPU mediante passthrough de Proxmox.
  2. VM con Docker – una máquina virtual (por ejemplo Ubuntu 22.04) que ejecuta Docker Compose con Sonarr, Radarr, SABnzbd, Prowlarr y Seerr. La VM monta los mismos datasets a través de NFS, garantizando que los paths sean idénticos dentro de los contenedores.
  3. ZFS layout – tres datasets en un pool espejo:
    • tank/media/movies
    • tank/media/tv
    • tank/downloads Mantener downloads dentro del mismo pool evita la copia innecesaria y permite hard links.
  4. Exportar NFS desde el host Proxmox y montar en la VM con la opción no_root_squash para que los UID/GID coincidan.
  5. Crear directorios de configuración con el UID/GID que usarán los contenedores (1000:1000 típico) antes de lanzar Docker Compose.

Paso a paso resumido

  1. Crear pool y datasets en Proxmox:

    zpool create -f -o ashift=12 tank mirror /dev/sda /dev/sdb
    zfs create -o compression=lz4 tank/media
    zfs create -o compression=lz4 tank/downloads
    zfs create -o mountpoint=/mnt/media/movies tank/media/movies
    zfs create -o mountpoint=/mnt/media/tv tank/media/tv
    
  2. Configurar export NFS en /etc/exports del host:

    /mnt/media  192.168.1.0/24(rw,no_root_squash,sync,no_subtree_check)
    /mnt/downloads 192.168.1.0/24(rw,no_root_squash,sync,no_subtree_check)
    

    Ejecutar exportfs -ra.

  3. Crear LXC para Plex:

    • Plantilla ubuntu.
    • Añadir bind mounts en la configuración del contenedor:
      mp0: /mnt/media/movies,mp=/media/movies
      mp0: /mnt/media/tv,mp=/media/tv
      
    • Passthrough de GPU:
      lxc.cgroup2.devices.allow: c 195:* rwm
      lxc.mount.entry: /dev/dri/card0 dev/dri/card0 none bind,optional,create=file
      
  4. Crear VM para Docker (2‑4 vCPU, 8 GB RAM, 100 GB disk):

    • Instalar Docker y Docker‑Compose.
    • Montar NFS en /media y /downloads:
      mkdir -p /media/movies /media/tv /downloads
      mount -t nfs4 host-proxmox:/mnt/media /media
      mount -t nfs4 host-proxmox:/mnt/downloads /downloads
      
    • Añadir entradas a /etc/fstab para persistir.
  5. Docker‑Compose (solo los servicios críticos):

    version: "3.8"
    services:
      sabnzbd:
        image: linuxserver/sabnzbd
        container_name: sabnzbd
        environment:
          - PUID=1000
          - PGID=1000
        volumes:
          - /downloads:/downloads
          - /config/sabnzbd:/config
        restart: unless-stopped
    
      sonarr:
        image: linuxserver/sonarr
        container_name: sonarr
        environment:
          - PUID=1000
          - PGID=1000
        volumes:
          - /downloads:/downloads
          - /media/tv:/tv
          - /config/sonarr:/config
        restart: unless-stopped
    
      radarr:
        image: linuxserver/radarr
        container_name: radarr
        environment:
          - PUID=1000
          - PGID=1000
        volumes:
          - /downloads:/downloads
          - /media/movies:/movies
          - /config/radarr:/config
        restart: unless-stopped
    
      prowlarr:
        image: linuxserver/prowlarr
        container_name: prowlarr
        environment:
          - PUID=1000
          - PGID=1000
        volumes:
          - /config/prowlarr:/config
        restart: unless-stopped
    
      seerr:
        image: ghcr.io/hotio/seerr
        container_name: seerr
        environment:
          - PUID=1000
          - PGID=1000
        volumes:
          - /config/seerr:/config
        restart: unless-stopped
    
  6. Ajustar permisos antes de docker compose up:

    sudo mkdir -p /config/{sabnzbd,sonarr,radarr,prowlarr,seerr}
    sudo chown -R 1000:1000 /config
    sudo chown -R 1000:1000 /downloads /media
    

Con esta separación, Plex nunca se ve afectado por reinicios de Docker y los arr pueden crear hard links directamente en los datasets ZFS.

Cuándo aplicar esta solución

  • Entorno doméstico o de pequeña oficina con un único nodo Proxmox y necesidad de alta disponibilidad de Plex.
  • Uso intensivo de descargas (Usenet o torrents) donde el espacio temporal de los archivos es comparable al tamaño de la biblioteca.
  • Requerimientos de transcodificación por hardware que obligan a pasar la GPU al hipervisor.
  • Necesidad de aislar servicios por motivos de seguridad o para simplificar actualizaciones (Docker vs LXC).

No es necesario si:

  • Solo se usa Plex sin automatización de descargas.
  • Todas las aplicaciones ya corren dentro de Docker y la pérdida de Plex durante reinicios es aceptable.
  • No se dispone de ZFS o de una GPU dedicada.

Código

# 1. Crear pool y datasets ZFS
zpool create -f -o ashift=12 tank mirror /dev/sda /dev/sdb
zfs create -o compression=lz4 tank/media
zfs create -o compression=lz4 tank/downloads
zfs create -o mountpoint=/mnt/media/movies tank/media/movies
zfs create -o mountpoint=/mnt/media/tv tank/media/tv

# 2. Configurar export NFS
cat <<EOF >> /etc/exports
/mnt/media 192.168.1.0/24(rw,no_root_squash,sync,no_subtree_check)
/mnt/downloads 192.168.1.0/24(rw,no_root_squash,sync,no_subtree_check)
EOF
exportfs -ra

# 3. Preparar directorios de configuración y permisos
mkdir -p /config/{sabnzbd,sonarr,radarr,prowlarr,seerr}
chown -R 1000:1000 /config /downloads /media

# 4. Iniciar Docker Compose
cd /path/to/compose
docker compose up -d

Verificación

  1. Hard links – después de que Sonarr o Radarr muevan un archivo, verifica que el inode sea el mismo:

    ls -i /downloads/example.mkv
    ls -i /media/tv/Show/Season\ 01/example.mkv
    

    Los números de inode deben coincidir.

  2. Plex siempre disponible – reinicia Docker en la VM:

    docker compose restart
    

    Accede a Plex en el LXC y confirma que la interfaz responde y la biblioteca sigue intacta.

  3. GPU passthrough – en el LXC, ejecuta lspci | grep -i nvidia (o AMD) y verifica que Plex muestra la GPU en sus logs de transcodificación.

Notas adicionales

  • Sincronización de tiempo: asegúrate de que el host Proxmox y la VM tengan NTP activo; diferencias de hora provocan fallos de certificados en SABnzbd.
  • Snapshots ZFS: crea snapshots de tank/media antes de grandes actualizaciones de Plex; permite volver atrás sin perder datos.
  • Limitar NFS version: