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/VFIO | No | Yes |
| GPU driver lives on | Proxmox host | Guest VM |
| Host can still use the GPU | Yes | No |
| Multiple guests at once | Yes, many containers | One VM only |
| Guest OS | Linux containers only | Any OS (Windows, Linux, BSD) |
| Isolation | Weaker (shared kernel) | Strong (hardware-isolated) |
| Driver versions | Container must match host | Independent |
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>:8006from any browser - A GPU with a working driver on the host:
- Intel iGPU or Arc (for example, an Intel Arc A380): the in-kernel
i915orxedriver - AMD: the in-kernel
amdgpudriver - NVIDIA (for example, an RTX 3060 12GB): the proprietary NVIDIA driver installed on the host, with
nvidia-smiworking
- Intel iGPU or Arc (for example, an Intel Arc A380): the in-kernel
- The GPU is not bound to
vfio-pcior 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 see | Meaning |
|---|---|
i915 or xe | Intel driver is loaded. Good. |
amdgpu | AMD driver is loaded. Good. |
nvidia | NVIDIA driver is loaded. Good. |
nouveau | Open NVIDIA driver. Install the proprietary driver on the host first. |
vfio-pci | GPU is reserved for VM passthrough. Go to Step 2. |
| No driver line | Driver 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.
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-pcioptions 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
What the output tells you:
226, 128is the major and minor number. You need these for the manual cgroup method.renderandvideoare the owning groups. Unprivileged containers need these GIDs mapped.card1instead ofcard0is common on newer kernels, where a simple framebuffer driver claimscard0first. Use whatever your host shows.- NVIDIA nodes often show
crw-rw-rw-(mode0666), 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-uvmuses a dynamic major number like508or510. It can change between driver versions./dev/kfdonly matters for AMD ROCm compute. VA-API transcoding doesn’t need it. Note its owning group too; it’s usuallyrender./dev/nvidia-caps/(if it exists) holds NVIDIA capability nodes such asnvidia-cap1andnvidia-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 to | Host UID 100000 (nobody special) | Host UID 0 (real root) |
| Impact of a container escape | Limited, unprivileged user on host | Full root on the Proxmox host |
| GPU permission setup | Needs GID mapping or Device Passthrough gid= option | Works with plain bind mounts |
| Proxmox’s guidance | Recommended | Only 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.
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.
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.
- 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
rendergroup inside the container (see the note below). - UID in CT: leave at
0(root). - Access Mode: leave at the default
0660for DRI nodes. Use0666for NVIDIA nodes so non-root users can reach them.
- GID in CT: the GID of the
- Click Add.
Repeat for each node you need:
| GPU | Typical workload | Nodes to add |
|---|---|---|
| Intel / AMD | VA-API / Quick Sync transcoding | /dev/dri/renderD128 (add /dev/dri/card1 only if an app insists) |
| AMD | ROCm compute (Ollama, PyTorch) | /dev/dri/renderD128 and /dev/kfd |
| NVIDIA | NVENC, 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.idmapreplaces Proxmox’s default mapping for this container, and it only works if/etc/subgidalready delegates the exact ranges you use. Before you change anything, back up the container config and/etc/subgid, and check for existing custom mappings orroot:entries (cat /etc/subgid,grep idmap /etc/pve/lxc/*.conf). On a cluster,/etc/subgidis 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.
Tip: The
optionalflag 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
.runinstaller (common on Proxmox hosts): use the same.runin the container with--no-kernel-module, as below. A.runinstall isn’t tracked byapt, 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.
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:
| Setting | Default | What it does |
|---|---|---|
devN: <path>,gid=,uid=,mode= | Not set | Proxmox-managed device passthrough. Creates the node in the container with the chosen ownership and mode. Handles cgroup access automatically. |
lxc.cgroup2.devices.allow | Not set (access denied) | Raw LXC rule allowing a device by type and major:minor, for example c 226:128 rwm. |
lxc.mount.entry | Not set | Raw LXC bind mount of a host device node into the container. |
lxc.idmap | Not set (Proxmox applies the standard 100000 offset) | Custom UID/GID mapping for unprivileged containers. Needed only with manual bind mounts. |
unprivileged | 1 for new containers | 1 = 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 withmode=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 everylxc.cgroup2.devices.allowline matches exactly. - Inside the container, run
ls -l /dev/dri. If the group isnogroup, switch to the Device Passthrough method withgid=set to the container’srenderGID. That’s the simplest fix. Otherwise, correct yourlxc.idmaplines and/etc/subgidentry. - 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-uvmorls -l /dev/dri. For NVIDIA UVM, runnvidia-modprobe -u -c=0if it’s missing. - Check the config with
pct config 101. Paths inlxc.mount.entrytake 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 --versionif it runs, or look at the installed.runversion. - Reinstall the matching user-space driver from the host’s channel: the same packages, or the matching
.runwith--no-kernel-module, as in Step 8. - Confirm every NVIDIA node you added (the five core nodes, plus any
nvidia-capsnodes your app needs) is present inside the container.
Tip: Pin the NVIDIA driver on the host so routine
apt upgraderuns 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-freeormesa-va-driversplusvainfofor Intel/AMD, ROCm user-space for AMD compute, or the version-matched NVIDIA user-space driver from the host’s channel (packages, or the.runwith--no-kernel-module). Never install a kernel module in an LXC. - Back up the config:
/etc/pve/lxc/<CTID>.conflives on the cluster filesystem and is included invzdumpbackups. 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.
| Step | Action | Applies To |
|---|---|---|
| 1–2 | Confirm host driver loaded, not vfio-pci | All GPUs |
| 3 | Record device nodes, major:minor, GIDs | All GPUs |
| 4 | Confirm unprivileged vs privileged | All containers |
| 5–6 | Stop CT, add devices (UI/pct set or .conf) | All GPUs |
| 7 | Start CT, check node ownership | All GPUs |
| 8 | Install user-space drivers | Intel/AMD: VA-API; NVIDIA: version-matched user-space driver; AMD compute: ROCm |
| 9 | Verify with vainfo / nvidia-smi / rocm-smi, add service user to groups | All GPUs |
Resources
- Proxmox VE Administration Guide, Linux Container chapter
- pct.conf reference (
devN,unprivileged, LXC keys) - Proxmox VE Roadmap and release notes
- What’s New in Proxmox VE 9.0 (video)
- What’s New in Proxmox VE 9.2 (video)
- Proxmox VE product overview
- NVIDIA Driver Downloads
- Intel Media Driver for VA-API (GitHub)
- AMD ROCm Documentation
- Proxmox Forum: Older NVIDIA GPUs LXC Passthrough Guide (community guide, retrieved 2026-10-08)