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:
- CPU y firmware con IOMMU habilitable.
- Un grupo IOMMU apto para la GPU y sus funciones asociadas.
- Un host Proxmox con acceso de recuperación.
- Una VM Linux con firmware UEFI/OVMF y tipo de máquina Q35.
- Una GPU NVIDIA compatible con el driver y el runtime de contenedores elegido.
- Acceso SSH a la VM si se elimina su adaptador de video virtual.
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:
- Existe un proceso de FFmpeg asociado a Jellyfin.
- La GPU muestra uso y memoria asignada durante la conversión.
- La CPU deja de ser el recurso dominante para el encode.
- La reproducción no presenta buffering sostenido ni errores de codec.
- La temperatura y el consumo se mantienen dentro de los límites del fabricante.
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:
- Definí una muestra de archivos y clientes.
- Registrá codec, resolución y bitrate de origen y destino.
- Aumentá streams de a uno.
- Observá GPU, VRAM, CPU, latencia, temperatura y buffering.
- 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.