Virtualization

How to Set Up Proxmox LXC GPU Passthrough on Proxmox VE 9 (Intel, AMD, NVIDIA)

25 min read

The Back Room Tech is reader-supported. We may earn a commission when you buy through links on our site. Learn more.

Proxmox LXC GPU passthrough gives a container direct access to your host’s GPU for Jellyfin transcoding, Ollama inference, or Frigate object detection. The host keeps the card, and VM passthrough would take it away. This guide covers the full setup on Proxmox VE 9: find the device nodes, add them through the web UI or /etc/pve/lxc/<CTID>.conf, install matching drivers in the container, and confirm it works with vainfo, nvidia-smi, or rocm-smi.

The short answer: keep the GPU driver loaded on the Proxmox host. Then expose its device files (/dev/dri/renderD128 for Intel/AMD, /dev/nvidia* for NVIDIA) to the container. On Proxmox VE 9, the easiest path is Resources > Add > Device Passthrough on an unprivileged container. You don’t need IOMMU or VFIO, and several containers can share the same GPU at once.

What Is LXC GPU Device Sharing?

LXC containers share the host’s Linux kernel. That means the host’s GPU driver already serves them. The container only needs permission to see and open the device files that driver has already created.

This works very differently from VM passthrough:

LXC device sharing (this guide)VM PCIe/VFIO passthrough
Needs IOMMU/VFIONoYes
GPU driver lives onProxmox hostGuest VM
Host can still use the GPUYesNo
Multiple guests at onceYes, many containersOne VM only
Guest OSLinux containers onlyAny OS (Windows, Linux, BSD)
IsolationWeaker (shared kernel)Strong (hardware-isolated)
Driver versionsContainer must match hostIndependent

Use LXC sharing for transcoding, local AI inference, and computer vision on Linux. It’s the better fit when one card has to serve several services. An Intel N100 mini PC running Jellyfin and Frigate side by side is the classic homelab case.

Use VM passthrough for a Windows guest, a gaming VM, or when you need a hard security boundary around the GPU workload. You can’t do both with the same card at the same time. A GPU bound to vfio-pci for a VM has no host driver, so there are no device nodes to share.

Prerequisites

  • A Proxmox VE 9.x host with root shell access (SSH or the node’s Shell in the web UI)
  • Access to the Proxmox web UI at https://<host-ip>:8006 from any browser
  • A GPU with a working driver on the host:
    • Intel iGPU or Arc (for example, an Intel Arc A380): the in-kernel i915 or xe driver
    • AMD: the in-kernel amdgpu driver
    • NVIDIA (for example, an RTX 3060 12GB): the proprietary NVIDIA driver installed on the host, with nvidia-smi working
  • The GPU is not bound to vfio-pci or assigned to a running VM
  • An existing LXC container (Debian 13 or Ubuntu 24.04 templates are the easiest) or a plan to create one
  • Internet access from inside the container to install packages

Reference Environment

This walkthrough targets Proxmox VE 9, which is based on Debian 13 “Trixie”. Container device passthrough in the UI first appeared in Proxmox VE 8.2, per the Proxmox VE Roadmap (retrieved 2026-10-08). It carries forward into 9.x. Point releases between 9.0 and 9.2 ship different kernel and LXC versions, so check yours before you start:

pveversion -v

proxmox-ve: 9.x.x (running kernel: 6.x.x-x-pve)
pve-manager: 9.x.x (running version: 9.x.x/…)
…
lxc-pve: 6.x.x
…

Note: The device numbers, group IDs, and outputs below are illustrative. They vary between hosts. Always use the values from your node.

Step-by-Step Guide

Step 1: Confirm the Host GPU and Driver

Run this on the Proxmox host. If the host can’t use the GPU, no container can either.

lspci -nnk | grep -A 3 -iE 'vga|3d|display'
# -nn shows vendor:device IDs; -k shows which kernel driver is bound

Expected output for an Intel iGPU:

00:02.0 VGA compatible controller [0300]: Intel Corporation … [8086:xxxx]
Subsystem: …
Kernel driver in use: i915
Kernel modules: i915, xe

Check the Kernel driver in use line:

You seeMeaning
i915 or xeIntel driver is loaded. Good.
amdgpuAMD driver is loaded. Good.
nvidiaNVIDIA driver is loaded. Good.
nouveauOpen NVIDIA driver. Install the proprietary driver on the host first.
vfio-pciGPU is reserved for VM passthrough. Go to Step 2.
No driver lineDriver not installed or blacklisted.

For NVIDIA, also confirm the host driver responds:

nvidia-smi

You should see a table listing your GPU and a Driver Version: field. Write that version down. You’ll need the exact same version inside the container in Step 8.

Proxmox VE 9 node Shell showing lspci -nnk output with an Intel integrated GPU and the Kernel driver in use: i915 line

Step 2: Make Sure the GPU Isn’t Bound to vfio-pci

If you once followed a VM passthrough guide, vfio-pci probably still claims the GPU. Check for leftover config:

grep -rE 'vfio|blacklist (i915|amdgpu|nvidia|nouveau)' /etc/modprobe.d/ /etc/modules /etc/modules-load.d/ 2>/dev/null
# 2>/dev/null hides "no such file" errors for paths that don't exist

If this prints lines like options vfio-pci ids=10de:xxxx or blacklist nvidia, the host can’t load its GPU driver. To switch the card back to the host:

  • Shut down any VM that has the GPU in its Hardware tab, and remove the PCI device from that VM.
  • Comment out or delete the vfio-pci options and GPU blacklist lines.
  • Rebuild the initramfs and reboot.
update-initramfs -u -k all
# -u updates existing images; -k all rebuilds for every installed kernel
reboot

Warning: This reboots the Proxmox host and stops every guest on it. Schedule it in a maintenance window.

After the reboot, rerun the lspci -nnk command from Step 1. The driver line should now show i915, xe, amdgpu, or nvidia.

Step 3: Identify the Device Nodes and Group IDs

The container config must match your host’s real device files. Don’t copy numbers from a forum post.

ls -l /dev/dri
ls -l /dev/nvidia* 2>/dev/null
ls -l /dev/kfd 2>/dev/null

Example output on an Intel host:

total 0
drwxr-xr-x 2 root root 80 Oct 8 09:12 by-path
crw-rw—- 1 root video 226, 1 Oct 8 09:12 card1
crw-rw—- 1 root render 226, 128 Oct 8 09:12 renderD128

Example output on an NVIDIA host:

crw-rw-rw- 1 root root 195, 0 Oct 8 09:12 /dev/nvidia0
crw-rw-rw- 1 root root 195, 255 Oct 8 09:12 /dev/nvidiactl
crw-rw-rw- 1 root root 195, 254 Oct 8 09:12 /dev/nvidia-modeset
crw-rw-rw- 1 root root 508, 0 Oct 8 09:12 /dev/nvidia-uvm
crw-rw-rw- 1 root root 508, 1 Oct 8 09:12 /dev/nvidia-uvm-tools

Proxmox host terminal showing output of ls -l /dev/dri and ls -l /dev/nvidia* with renderD128 and nvidia0 entries, their major/minor numbers, and owning groups visible

What the output tells you:

  • 226, 128 is the major and minor number. You need these for the manual cgroup method.
  • render and video are the owning groups. Unprivileged containers need these GIDs mapped.
  • card1 instead of card0 is common on newer kernels, where a simple framebuffer driver claims card0 first. Use whatever your host shows.
  • NVIDIA nodes often show crw-rw-rw- (mode 0666), which means world read/write, as in the example above. When they do, NVIDIA permissions are easier in unprivileged containers. Check your own output, though: udev rules and driver options can make them stricter, so don’t rely on world-writable nodes without looking.
  • /nvidia-uvm uses a dynamic major number like 508 or 510. It can change between driver versions.
  • /dev/kfd only matters for AMD ROCm compute. VA-API transcoding doesn’t need it. Note its owning group too; it’s usually render.
  • /dev/nvidia-caps/ (if it exists) holds NVIDIA capability nodes such as nvidia-cap1 and nvidia-cap2. They’re used for Multi-Instance GPU (MIG) on data-center cards. Typical GeForce/RTX NVENC, CUDA, and Ollama setups don’t need them, but if your app’s docs list them, pass them through as well.

Now get the host’s numeric group IDs:

getent group render video

render:x:104:
video:x:44:

If /dev/nvidia-uvm is missing, the UVM module hasn’t created its node yet. CUDA and Ollama need it. Create it now:

nvidia-modprobe -u -c=0
# -u loads nvidia-uvm and creates /dev/nvidia-uvm*; -c=0 creates the node for GPU 0

To make that stick across reboots, enable the nvidia-persistenced service if your driver install provided it. You can also run the command above from a systemd unit at boot.

Step 4: Choose Privileged or Unprivileged (and Check What You Have)

Most quick guides skip this decision. It’s also the one that matters most for security.

Unprivileged (default)Privileged
Container root maps toHost UID 100000 (nobody special)Host UID 0 (real root)
Impact of a container escapeLimited, unprivileged user on hostFull root on the Proxmox host
GPU permission setupNeeds GID mapping or Device Passthrough gid= optionWorks with plain bind mounts
Proxmox’s guidanceRecommendedOnly for trusted workloads (admin guide)

My recommendation: use unprivileged containers. Before Proxmox VE 8.1 added the devN option (8.2 then put it in the web UI), unprivileged GPU access meant fiddly lxc.idmap lines. That’s why so many older guides say “just make it privileged.” Device Passthrough removes most of that pain. It creates the node inside the container with whatever GID you choose. You get GPU access without giving container root the keys to your hypervisor.

Privileged still has a place. It’s fine for a throwaway lab container, or when a tool needs capabilities beyond device access. But a Jellyfin container with a web UI on your network is exactly what you don’t want running as host root.

Check an existing container (web): select the container, open Options, and look at the Unprivileged container row.

Proxmox VE 9 web UI with an LXC container selected, Options tab open, showing the "Unprivileged container" row and its Yes/No value highlighted

Check from the shell (linux):

pct config 101 | grep unprivileged
# replace 101 with your container ID

unprivileged: 1

1 means unprivileged. No line at all means privileged.

Note: You can’t flip this setting on an existing container from the UI. To change it, back up the container and restore it with the Unprivileged container box checked or unchecked. That option is in the backup restore dialog. For a new container, the checkbox is on the General tab of the Create CT wizard and is on by default.

Proxmox VE 9 Create CT wizard on the General tab with the "Unprivileged container" checkbox highlighted

Step 5: Stop the Container

Device changes apply when the container starts. Stop it first so the change is clean and predictable.

pct stop 101
pct status 101

status: stopped

Step 6: Add the GPU Device to the Container

Pick one method per device: the web UI (or its pct set equivalent) or manual .conf entries. Don’t define the same node both ways.

Web (Proxmox web UI): Recommended

  • In the left tree, select your container, for example 101 (jellyfin).
  • Click Resources.
  • Click Add > Device Passthrough.
Proxmox VE 9 web UI with a stopped LXC container selected, Resources tab open, Add dropdown expanded with "Device Passthrough" option highlighted
  • In Device Path, enter the host path from Step 3, for example /dev/dri/renderD128.
  • Tick Advanced to show the extra fields:
    • GID in CT: the GID of the render group inside the container (see the note below).
    • UID in CT: leave at 0 (root).
    • Access Mode: leave at the default 0660 for DRI nodes. Use 0666 for NVIDIA nodes so non-root users can reach them.
  • Click Add.
Proxmox VE 9 Device Passthrough dialog with Advanced checked, showing Device Path field filled with /dev/dri/renderD128 and the Access Mode, UID in CT, and GID in CT fields

Repeat for each node you need:

GPUTypical workloadNodes to add
Intel / AMDVA-API / Quick Sync transcoding/dev/dri/renderD128 (add /dev/dri/card1 only if an app insists)
AMDROCm compute (Ollama, PyTorch)/dev/dri/renderD128 and /dev/kfd
NVIDIANVENC, CUDA, Ollama/dev/nvidia0, /dev/nvidiactl, /dev/nvidia-uvm, /dev/nvidia-uvm-tools, /dev/nvidia-modeset (plus any /dev/nvidia-caps/* nodes your app requires)

These are the core nodes for a single-GPU host. On a multi-GPU host, add /dev/nvidia1 and so on for each card the container should see. For AMD ROCm, give /dev/kfd the same GID in CT as the render node, because ROCm user-space opens both.

Finding the right GID in CT: the container’s render group often has a different number than the host’s. Check it from the host while the container is stopped:

pct mount 101
grep -E '^(render|video):' /var/lib/lxc/101/rootfs/etc/group
pct unmount 101
# pct mount exposes the stopped container's filesystem under /var/lib/lxc/<CTID>/rootfs

video:x:44:
render:x:993:

In this example you’d enter 993 as GID in CT. If the container has no render group, use the video GID. You can also create a render group later and come back to adjust this.

CLI equivalent (linux): the web UI writes devN: lines to the config. You can do the same thing with pct set:

pct set 101 -dev0 /dev/dri/renderD128,gid=993
# -dev0 is the first device slot; gid= is the GID inside the container

For NVIDIA:

pct set 101 -dev0 /dev/nvidia0,mode=0666
pct set 101 -dev1 /dev/nvidiactl,mode=0666
pct set 101 -dev2 /dev/nvidia-uvm,mode=0666
pct set 101 -dev3 /dev/nvidia-uvm-tools,mode=0666
pct set 101 -dev4 /dev/nvidia-modeset,mode=0666

For AMD ROCm compute (use the container’s render GID for both):

pct set 101 -dev0 /dev/dri/renderD128,gid=993
pct set 101 -dev1 /dev/kfd,gid=993

The single-dash option form matches the examples in the Proxmox admin guide (for example, pct set 100 -mp0 ...). Run pct help set on your host to see the exact options your release supports.

Why this is the better method: Proxmox creates the device node inside the container’s /dev with the owner and mode you pick. It also handles the cgroup permission for you. You don’t need lxc.idmap or edits to /etc/subgid. It works the same for privileged and unprivileged containers. See the dev[n] option in the pct.conf reference.

Linux (Proxmox host shell): Manual .conf Method

Use this when you need something the UI doesn’t offer, like a wildcard cgroup rule for every NVIDIA minor number. Most pre-8.2 guides work this way too, so it helps to recognize it.

Open the config on the host:

nano /etc/pve/lxc/101.conf

Privileged container, Intel/AMD: add these lines at the end, before any [snapshot] sections.

# /etc/pve/lxc/101.conf
# Allow character device major 226, minor 128 (renderD128): read, write, mknod
lxc.cgroup2.devices.allow: c 226:128 rwm
# Bind-mount the host node into the container; create=file makes an empty target file first
lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file

Privileged or unprivileged container, NVIDIA:

# /etc/pve/lxc/101.conf
# 195 = nvidia0, nvidiactl, nvidia-modeset; the * wildcard covers all minors
lxc.cgroup2.devices.allow: c 195:* rwm
# nvidia-uvm major is dynamic: 508 is only this example host's value.
# Replace it with YOUR major from Step 3 (or: grep nvidia-uvm /proc/devices)
lxc.cgroup2.devices.allow: c 508:* rwm
lxc.mount.entry: /dev/nvidia0 dev/nvidia0 none bind,optional,create=file
lxc.mount.entry: /dev/nvidiactl dev/nvidiactl none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-modeset dev/nvidia-modeset none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-uvm dev/nvidia-uvm none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-uvm-tools dev/nvidia-uvm-tools none bind,optional,create=file

If your host’s NVIDIA nodes are 0666 (check the Step 3 output), unprivileged containers can open them without any ID mapping. If they’re stricter, use the Device Passthrough method with mode=0666 or a gid= instead. If you also need /dev/nvidia-caps/* nodes, add a matching lxc.cgroup2.devices.allow line for their major number (from ls -l /dev/nvidia-caps) and one lxc.mount.entry per node.

Unprivileged container, Intel/AMD: a bind-mounted node keeps its host ownership (root:render, GID 104). An unprivileged container can’t see host GID 104. The node shows up as nobody:nogroup, and access fails. The fix is to map one container GID straight through to host GID 104.

Warning: Treat the mapping below as a pattern, not a copy-paste recipe. A custom lxc.idmap replaces Proxmox’s default mapping for this container, and it only works if /etc/subgid already delegates the exact ranges you use. Before you change anything, back up the container config and /etc/subgid, and check for existing custom mappings or root: entries (cat /etc/subgid, grep idmap /etc/pve/lxc/*.conf). On a cluster, /etc/subgid is per node, so every node the container can migrate to needs the same entry. If any of that is unclear in your environment, use the Device Passthrough method instead. It needs none of this.

This example maps container GID 104 to host GID 104 and keeps everything else on the normal 100000+ range:

# /etc/pve/lxc/101.conf
lxc.cgroup2.devices.allow: c 226:128 rwm
lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file
# u/g = user/group; format is: <type> <first CT id> <first host id> <count>
lxc.idmap: u 0 100000 65536
lxc.idmap: g 0 100000 104
lxc.idmap: g 104 104 1
lxc.idmap: g 105 100105 65431

The ranges must cover 0–65535 with no gaps or overlaps: 104 + 1 + 65431 = 65536. If your host render GID isn’t 104, redo the math for all three g lines.

Then allow root to delegate that host GID. Add the line only if it isn’t already there, so repeated runs don’t create duplicates:

grep -qx 'root:104:1' /etc/subgid || echo 'root:104:1' >> /etc/subgid
cat /etc/subgid

root:100000:65536
root:104:1

If your file shows a different default range than root:100000:65536, adjust the lxc.idmap lines to match it instead of copying the numbers above. Inside the container, the render group must use GID 104 for this to line up. You’ll handle that in Step 8. If the container then refuses to start with an idmap error, recheck the ranges against /etc/subgid or remove the custom lxc.idmap lines and switch to Device Passthrough.

Terminal showing cat /etc/pve/lxc/101.conf with the lxc.cgroup2.devices.allow and lxc.mount.entry lines for renderD128 highlighted

Tip: The optional flag tells LXC to skip a missing node silently instead of failing to start. That’s handy after a driver update, but it also hides mistakes. If a node never appears in the container, check the path spelling first.

Step 7: Start the Container and Check the Nodes

pct start 101
pct exec 101 -- ls -l /dev/dri /dev/nvidia* 2>/dev/null
# pct exec runs a single command inside the container

Expected for an unprivileged Intel container using Device Passthrough with gid=993:

/dev/dri:
total 0
crw-rw—- 1 root render 226, 128 Oct 8 09:30 renderD128

The group must be a real group, render or video. If you see nogroup, the GID mapping is wrong. Jump to the “Permission denied” fix in Troubleshooting.

If the container won’t start and throws a device error, a configured path doesn’t exist on the host. Device Passthrough entries aren’t optional. Recheck Step 3.

Step 8: Install User-Space Drivers Inside the Container

The kernel driver lives on the host. The container only needs the user-space libraries that apps call. Open a shell in the container:

pct enter 101

All commands from here to the end of Step 9 run inside the container as root.

Intel (Debian 13 container): the full Quick Sync driver lives in the non-free component. Enable it first:

sed -i 's/^Components: main.*/Components: main contrib non-free non-free-firmware/' /etc/apt/sources.list.d/debian.sources
apt update
apt install -y vainfo intel-media-va-driver-non-free

On templates that use /etc/apt/sources.list, add non-free to the existing lines instead. For Gen 7–8 era Intel iGPUs, install i965-va-driver instead of intel-media-va-driver-non-free. Ubuntu 24.04 containers ship intel-media-va-driver-non-free in the multiverse repository.

AMD (VA-API transcoding):

apt update
apt install -y vainfo mesa-va-drivers

AMD (ROCm compute): follow AMD’s ROCm installation docs for your container’s distro. The container needs /dev/kfd and the render node. Check AMD’s compatibility matrix before you buy a card for this. ROCm officially supports far fewer consumer GPUs than CUDA does.

NVIDIA: install the exact driver version the host runs, without its kernel module, from the same channel the host driver came from. First read the version on the host:

nvidia-smi --query-gpu=driver_version --format=csv,noheader

Pick the container method that matches the host:

  • Host driver from distribution packages or a vendor/data-center repository: add the same repository in the container and install only that version’s user-space packages (libraries and nvidia-smi), not the kernel/DKMS packages. Packages keep upgrades managed, so prefer this when the host uses them.
  • Host driver from NVIDIA’s .run installer (common on Proxmox hosts): use the same .run in the container with --no-kernel-module, as below. A .run install isn’t tracked by apt, so you’ll need to rerun it by hand after every host driver update.

For the .run path, set the host’s version inside the container and install the matching package from NVIDIA’s driver downloads:

apt update
apt install -y wget kmod
DRIVER_VERSION="YOUR_HOST_DRIVER_VERSION"   # paste the exact value from the host
wget "https://us.download.nvidia.com/XFree86/Linux-x86_64/${DRIVER_VERSION}/NVIDIA-Linux-x86_64-${DRIVER_VERSION}.run"
chmod +x "NVIDIA-Linux-x86_64-${DRIVER_VERSION}.run"
./"NVIDIA-Linux-x86_64-${DRIVER_VERSION}.run" --no-kernel-module
# --no-kernel-module installs only user-space libraries and tools; the host already provides the kernel module

Accept the license prompts. Answer “No” to any offer to update the X configuration. Never mix channels, such as a packaged driver on the host and a .run in the container. Mixed versions are the number one cause of nvidia-smi failures. See the troubleshooting section below.

Unprivileged + manual idmap only: make the container’s render group match the mapped GID 104:

groupmod -g 104 render 2>/dev/null || groupadd -g 104 render

If another group inside the container already has GID 104, pick a different shared GID. Then adjust the lxc.idmap lines and /etc/subgid to match.

Step 9: Verify GPU Access Inside the Container

Intel / AMD:

vainfo --display drm --device /dev/dri/renderD128
# --display drm skips X11/Wayland, which a headless container doesn't have

Expected output (trimmed):

libva info: VA-API version 1.x.x
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
libva info: va_openDriver() returns 0
vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics – …
vainfo: Supported profile and entrypoints
VAProfileH264Main : VAEntrypointVLD
VAProfileH264Main : VAEntrypointEncSlice
VAProfileHEVCMain : VAEntrypointVLD
…

va_openDriver() returns 0 plus a list of VAEntrypointEncSlice profiles means hardware encode works.

NVIDIA:

nvidia-smi

You should see the same GPU model and the same Driver Version as on the host. The process list stays empty until something uses the GPU.

AMD ROCm:

rocm-smi

On newer ROCm releases, rocm-smi may not be installed. AMD SMI is its successor, so run amd-smi list instead.

You should see your GPU listed with temperature, power, and VRAM usage.

Terminal session inside the jellyfin LXC container showing nvidia-smi output with the GeForce RTX 3060 and its driver version

Last step: the service user. Running as root proves the device works. Your app probably runs as its own user, though, like jellyfin or ollama. Add that user to the right group:

usermod -aG render,video jellyfin
id jellyfin
# -aG appends supplementary groups without removing existing ones

uid=105(jellyfin) gid=110(jellyfin) groups=110(jellyfin),44(video),993(render)

Restart the service afterward so it picks up the new group membership.

Configuration Reference

These settings control GPU sharing in /etc/pve/lxc/<CTID>.conf:

SettingDefaultWhat it does
devN: <path>,gid=,uid=,mode=Not setProxmox-managed device passthrough. Creates the node in the container with the chosen ownership and mode. Handles cgroup access automatically.
lxc.cgroup2.devices.allowNot set (access denied)Raw LXC rule allowing a device by type and major:minor, for example c 226:128 rwm.
lxc.mount.entryNot setRaw LXC bind mount of a host device node into the container.
lxc.idmapNot set (Proxmox applies the standard 100000 offset)Custom UID/GID mapping for unprivileged containers. Needed only with manual bind mounts.
unprivileged1 for new containers1 = unprivileged (recommended), absent or 0 = privileged.

Common patterns:

  • Transcoding container (Jellyfin, Plex, Frigate on Intel/AMD): one dev0: /dev/dri/renderD128,gid=<render GID in CT> line. That’s usually all you need.
  • AI container on NVIDIA (Ollama, llama.cpp, ComfyUI): five devN: lines for the NVIDIA nodes with mode=0666, plus matching user-space drivers. Plan for VRAM before cores. A 12 GB card like the RTX 3060 12GB comfortably fits 7–8B parameter models at common quantizations.
  • One GPU, many containers: add the same devN: lines to each container. The host driver schedules work from all of them. There’s no hard partitioning, though. One container can eat all the VRAM and starve the others.

Tips and Troubleshooting

Container reports “Permission denied” when accessing the GPU

Symptoms: vainfo prints failed to open /dev/dri/renderD128 or Permission denied. Jellyfin logs show a VA-API init failure.

Why it happens: either the cgroup rule doesn’t match the device’s major:minor, or nogroup owns the node inside an unprivileged container because the GID isn’t mapped.

Fix:

  • On the host, recheck the numbers with ls -l /dev/dri. Make sure every lxc.cgroup2.devices.allow line matches exactly.
  • Inside the container, run ls -l /dev/dri. If the group is nogroup, switch to the Device Passthrough method with gid= set to the container’s render GID. That’s the simplest fix. Otherwise, correct your lxc.idmap lines and /etc/subgid entry.
  • Confirm the service user is in that group with id <user>. Then restart the service.

Device nodes are missing inside the container

Symptoms: ls /dev/dri or ls /dev/nvidia0 inside the container says No such file or directory.

Why it happens: the lxc.mount.entry line is missing, has a typo, or points at a node that didn’t exist on the host when the container started. The optional flag hides that last case. You may also have edited the config without restarting.

Fix:

  • On the host, confirm the node exists with ls -l /dev/nvidia-uvm or ls -l /dev/dri. For NVIDIA UVM, run nvidia-modprobe -u -c=0 if it’s missing.
  • Check the config with pct config 101. Paths in lxc.mount.entry take the form /dev/x dev/x, with no leading slash on the second path.
  • Run pct stop 101 && pct start 101. A reboot from inside the container doesn’t always reapply LXC settings. A full stop and start from the host does.

nvidia-smi works on the host but fails inside the container

Symptoms: Failed to initialize NVML: Driver/library version mismatch, or NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver.

Why it happens: the user-space libraries in the container don’t match the host kernel module version. The other common cause is a missing node, usually /dev/nvidiactl or /dev/nvidia-uvm. This often shows up right after a host driver upgrade, because containers keep the old libraries.

Fix:

  • On the host: nvidia-smi --query-gpu=driver_version --format=csv,noheader.
  • In the container, check what’s installed with nvidia-smi --version if it runs, or look at the installed .run version.
  • Reinstall the matching user-space driver from the host’s channel: the same packages, or the matching .run with --no-kernel-module, as in Step 8.
  • Confirm every NVIDIA node you added (the five core nodes, plus any nvidia-caps nodes your app needs) is present inside the container.

Tip: Pin the NVIDIA driver on the host so routine apt upgrade runs don’t silently break every GPU container. Then upgrade the host and all containers together, on purpose.

The GPU doesn’t appear on the host at all

Symptoms: /dev/dri has no renderD128, /dev/nvidia0 doesn’t exist, and lspci -k shows Kernel driver in use: vfio-pci.

Why it happens: the card is still set up for VM passthrough. A GPU can’t serve a VFIO-passthrough VM and host-driver LXC sharing at once.

Fix: remove the PCI device from the VM. Delete the vfio-pci options and driver blacklist entries. Run update-initramfs -u -k all and reboot, as in Step 2. Confirm lspci -k shows the vendor driver before you return to container setup.

Transcoding works in one container but not a second one on the same GPU

Why it happens: the second container’s service user isn’t in its own render group. Or its gid= value was copied from the first container, which uses a different GID. Device numbering can also change after a host reboot or kernel update. For example, card0 can become card1.

Fix: run id <service-user> inside the failing container, check grep render /etc/group there, and adjust its devN gid= to match. After any host reboot or kernel upgrade, rerun ls -l /dev/dri on the host to confirm the paths haven’t changed.

Quick Answers

  • Do I need IOMMU/VFIO for LXC GPU sharing? No. IOMMU and VFIO are for handing a PCI device to a VM. LXC sharing uses the host’s normal driver. In fact, VFIO binding breaks LXC sharing.
  • Can multiple containers use the same GPU at once? Yes. Add the device to each container. They share VRAM and compute time with no enforced limits.
  • Can I still use the GPU in a VM? Not at the same time via PCIe passthrough. You’d have to stop the containers, bind the card to vfio-pci, and reverse the process to go back. If you need both, the realistic answer is a second GPU.
  • Which packages go inside the container? Only user-space pieces. Use intel-media-va-driver-non-free or mesa-va-drivers plus vainfo for Intel/AMD, ROCm user-space for AMD compute, or the version-matched NVIDIA user-space driver from the host’s channel (packages, or the .run with --no-kernel-module). Never install a kernel module in an LXC.
  • Back up the config: /etc/pve/lxc/<CTID>.conf lives on the cluster filesystem and is included in vzdump backups. Still, keep a copy of your device lines somewhere. They’re the fiddliest part to recreate.

Wrapping Up

You now have a GPU shared into an LXC container on Proxmox VE 9, and the host keeps its driver. Jellyfin, Ollama, and Frigate can all point at the same card, with no single VM hogging it. For most homelabs, that’s a better default than VFIO passthrough. My take: use the Device Passthrough UI option on an unprivileged container. Reach for hand-written lxc.idmap lines or privileged mode only when you have a specific reason.

The one recurring cost is NVIDIA driver upkeep. Every host driver update means updating every container to match, so budget a few minutes per container each time.

StepActionApplies To
1–2Confirm host driver loaded, not vfio-pciAll GPUs
3Record device nodes, major:minor, GIDsAll GPUs
4Confirm unprivileged vs privilegedAll containers
5–6Stop CT, add devices (UI/pct set or .conf)All GPUs
7Start CT, check node ownershipAll GPUs
8Install user-space driversIntel/AMD: VA-API; NVIDIA: version-matched user-space driver; AMD compute: ROCm
9Verify with vainfo / nvidia-smi / rocm-smi, add service user to groupsAll GPUs

Resources