← Volver al blog

// GUÍA TÉCNICA

Passthrough de GPU NVIDIA en Proxmox para transcoding con Jellyfin

Guía para asignar una GPU NVIDIA a una VM Linux, exponerla a Docker y validar transcoding por hardware sin acoplar la aplicación al host.

2026-09-2225 min de lecturaRead in English ↗
ProxmoxVFIONVIDIADockerJellyfin

Qué resuelve esta arquitectura

El transcoding por software puede convertir un servidor multimedia en una carga intensiva de CPU. Asignar una GPU directamente a una máquina virtual permite conservar la separación entre el hipervisor y la aplicación, mientras el contenedor usa los motores dedicados de codificación y decodificación.

El recorrido de esta guía es:

Host Proxmox -> VFIO -> VM Linux -> driver NVIDIA -> Container Toolkit -> Jellyfin

La GPU queda reservada para una sola VM. No es una solución para compartir el mismo dispositivo entre múltiples VMs ni una receta universal: el resultado depende del chipset, los grupos IOMMU, la GPU, los codecs y el tipo de clientes.

Trabajá con consola física o acceso remoto alternativo al host. Un cambio incorrecto de bootloader, driver o configuración PCIe puede dejar inaccesible la VM o el propio servidor hasta el siguiente reinicio.

Conceptos que conviene separar

IOMMU, VFIO y passthrough

IOMMU permite al kernel aislar dispositivos PCIe y sus accesos de memoria. Intel lo denomina VT-d y AMD, AMD-Vi. VFIO es el framework de Linux que toma un dispositivo PCIe y lo prepara para que QEMU/KVM se lo entregue a una VM.

Para que el aislamiento sea válido, hay que revisar el grupo IOMMU completo. Una GPU suele presentar al menos dos funciones: video y audio. Si comparte grupo con otros dispositivos, no es seguro ni siempre posible asignar sólo una de ellas.

NVENC y NVDEC

NVENC codifica video y NVDEC lo decodifica. Son bloques dedicados de la GPU: no equivalen a renderizar ni a ejecutar una carga CUDA. Jellyfin puede usarlos cuando un cliente no soporta el codec original, necesita reducir bitrate, requiere subtítulos incrustados o convierte HDR a SDR.

Antes de invertir tiempo en passthrough, verificá si el cliente puede usar direct play. Evitar una transcodificación innecesaria suele ser la mejora más eficiente.

Requisitos y decisiones previas

Necesitás:

No uses identificadores de ejemplos como valores reales. En los comandos siguientes reemplazá:

Placeholder Ejemplo de significado
<PCI_ADDRESS> Dirección PCI de la GPU, por ejemplo 01:00.0
<GPU_IDS> IDs vendor:device de video y audio
<VM_ID> Identificador local de la VM
<STORAGE> Storage que contiene el disco EFI de la VM

1. Validar IOMMU en el host

Primero confirmá qué reporta el kernel:

dmesg | grep -Ei 'DMAR|IOMMU|AMD-Vi'
find /sys/kernel/iommu_groups/ -type l | wc -l

Si no hay grupos, habilitá VT-d o AMD-Vi en BIOS/UEFI. Luego agregá el parámetro apropiado al arranque del kernel:

# Intel
intel_iommu=on iommu=pt

# AMD
amd_iommu=on iommu=pt

La forma de persistir ese cambio depende del método de boot de Proxmox. En instalaciones basadas en GRUB, actualizá la configuración de GRUB; en instalaciones con proxmox-boot-tool, actualizá la entrada EFI según la documentación oficial. Reiniciá y verificá /proc/cmdline antes de seguir.

cat /proc/cmdline

2. Inspeccionar grupos IOMMU

Listá los dispositivos por grupo antes de asociar nada a VFIO:

for device in /sys/kernel/iommu_groups/*/devices/*; do
  group=${device#*/iommu_groups/*}
  group=${group%%/*}
  printf 'Group %s: ' "$group"
  lspci -nns "${device##*/}"
done

Anotá la función de video, la de audio y sus IDs numéricos:

lspci -nn | grep -Ei 'VGA|3D|Audio.*NVIDIA'
lspci -k -s <PCI_ADDRESS>

Un grupo exclusivo para GPU y audio suele ser ideal. Si el grupo contiene un controlador indispensable, no uses atajos que relajen ACS sin entender sus implicancias: puede degradar el aislamiento que justamente buscás obtener.

3. Reservar la GPU para VFIO

El host no debe cargar nouveau ni el driver propietario para la GPU que vas a entregar a la VM. Creá archivos de configuración específicos y documentá el cambio en tu control de cambios.

sudo tee /etc/modprobe.d/blacklist-gpu-passthrough.conf >/dev/null <<'EOF'
blacklist nouveau
blacklist nvidia
blacklist nvidia_drm
blacklist nvidia_modeset
EOF

sudo tee /etc/modprobe.d/vfio.conf >/dev/null <<'EOF'
options vfio-pci ids=<GPU_IDS>
EOF

Agregá los módulos VFIO al arranque y regenerá initramfs:

printf '%s\n' vfio vfio_iommu_type1 vfio_pci | sudo tee -a /etc/modules
sudo update-initramfs -u -k all
sudo reboot

Después del reinicio, la verificación importante no es que la GPU aparezca en lspci, sino que el driver en uso sea vfio-pci:

lspci -k -s <PCI_ADDRESS>

4. Preparar la VM

Configurá la VM con Q35 y OVMF. Q35 expone un bus PCIe moderno y OVMF aporta firmware UEFI. El comando exacto depende del estado actual de la VM, así que inspeccionala antes de modificarla:

qm config <VM_ID>

Una asignación típica toma la dirección base de la GPU:

qm set <VM_ID> -hostpci0 <PCI_ADDRESS>,pcie=1

x-vga=1 puede ser necesario para ciertos casos, pero vuelve a la GPU el adaptador principal de la VM. Si además eliminás el video virtual, la consola noVNC dejará de ser una vía de recuperación. Conservá SSH, una consola serie o un procedimiento documentado para revertir el cambio.

Secure Boot

Los módulos NVIDIA compilados por DKMS pueden ser rechazados si Secure Boot no confía en su firma. La opción preferida es enrolar una clave MOK o usar paquetes firmados por la distribución. Desactivar Secure Boot puede ser una decisión válida en un entorno controlado, pero debe tener justificación, acceso físico restringido y un plan de reversión; no es el paso por defecto de esta guía.

5. Instalar y validar el driver en la VM

La VM es un sistema independiente del host: su driver nouveau también debe evitarse si vas a usar el controlador de NVIDIA. Instalá el driver desde el repositorio de tu distribución, junto con los headers del kernel en ejecución.

sudo apt update
sudo apt install -y linux-headers-$(uname -r) nvidia-driver
sudo reboot

Tras reiniciar, validá la detección y el estado de la GPU:

nvidia-smi

No avances a Docker si este comando falla. Primero verificá que la VM reciba el dispositivo, que el módulo cargue y que no haya un conflicto de Secure Boot o de versiones entre kernel y driver.

6. Exponer la GPU a Docker

NVIDIA Container Toolkit conecta el runtime de contenedores con el driver instalado en la VM. Seguí el método de instalación vigente en la documentación oficial de NVIDIA y luego configurá Docker:

sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

Probá el acceso antes de involucrar a Jellyfin:

docker run --rm --gpus all nvidia/cuda:<TAG> nvidia-smi

Elegí una etiqueta de imagen soportada y mantenida. Si el contenedor no ve la GPU, revisá nvidia-smi en la VM, el runtime configurado y los logs de Docker antes de cambiar el compose de la aplicación.

7. Declarar la GPU en Docker Compose

La sintaxis puede variar según la versión de Compose. Este ejemplo muestra la intención, no una topología de producción:

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    environment:
      NVIDIA_VISIBLE_DEVICES: all
      NVIDIA_DRIVER_CAPABILITIES: compute,video,utility
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /srv/media:/media:ro

video habilita NVENC/NVDEC; utility permite herramientas como nvidia-smi. Montá el contenido multimedia en modo lectura cuando el servicio no necesite modificarlo. Evitá publicar rutas reales, usuarios o secretos dentro de un compose.

Recreá el servicio y comprobá el dispositivo desde el contenedor:

docker compose up -d jellyfin
docker exec jellyfin nvidia-smi

8. Configurar y verificar Jellyfin

En el panel de reproducción, elegí aceleración NVIDIA y activá sólo los codecs que tu GPU soporte. La compatibilidad con AV1, HEVC y tone mapping depende de la generación de la GPU y del build de FFmpeg incluido por Jellyfin.

La prueba debe forzar una transcodificación real. Reproducir un archivo compatible en un cliente compatible sólo mide direct play, que es correcto pero no ejercita la GPU. Durante una reproducción que requiera conversión, observá:

watch -n 1 nvidia-smi

Validá estas señales:

Troubleshooting por capas

IOMMU no aparece

Revisá firmware, parámetro de kernel y método de boot. No asumas que editar GRUB actualiza el cargador usado por tu instalación: comprobá /proc/cmdline después del reinicio.

vfio-pci no toma la GPU

Confirmá los IDs de video y audio con lspci -nn, verificá que no haya driver gráfico cargado y regenerá initramfs. Los IDs son más estables que una dirección PCI cuando cambia el hardware, pero ambos deben validarse tras cambios físicos.

La VM no inicia o pierde consola

Revisá Q35, OVMF, la asignación PCIe y el procedimiento de recuperación. Si usaste x-vga, probá retirarlo temporalmente y reactivá un adaptador de video virtual desde una consola del host.

nvidia-smi falla en la VM

Verificá dmesg, versión de kernel, headers, DKMS y estado de Secure Boot. No mezcles paquetes de repositorios incompatibles ni instales un driver manual sobre uno administrado por la distribución sin un plan de actualización.

Docker no detecta la GPU

La cadena de diagnóstico es: GPU visible en VM, driver funcional, nvidia-ctk aplicado, Docker reiniciado y prueba con una imagen CUDA. Corregí la primera capa que falle.

Jellyfin sigue usando CPU

Confirmá desde el panel que la reproducción está transcodificando. Revisá los logs de FFmpeg, codecs habilitados, permisos sobre dispositivos y soporte real de la GPU para el codec solicitado. Si el cliente admite el archivo original, preferí direct play en lugar de forzar una conversión.

Medir capacidad sin prometer números universales

La capacidad depende de resolución, codec origen, codec destino, HDR, subtítulos, bitrate, versión de FFmpeg, red y temperatura. En lugar de declarar una cantidad fija de usuarios, medí un escenario por vez:

  1. Definí una muestra de archivos y clientes.
  2. Registrá codec, resolución y bitrate de origen y destino.
  3. Aumentá streams de a uno.
  4. Observá GPU, VRAM, CPU, latencia, temperatura y buffering.
  5. Detené la prueba antes de degradar el servicio.

Documentar método, límites y condiciones es más útil que publicar una cifra aislada.

Operación segura y reversión

Guardá una copia de la configuración de la VM antes de tocar PCIe, mantené acceso alternativo al host y registrá cada cambio de bootloader, initramfs, driver y runtime. Para revertir, apagá la VM, retirale el dispositivo PCIe, quitá el binding VFIO, regenerá initramfs y validá que el host recupere el driver esperado.

El objetivo final no es que una GPU aparezca en un dashboard: es operar una cadena entendible, actualizable y recuperable desde el hipervisor hasta el contenedor.