
Running TrueNAS on Proxmox VE is the great way to get great VM and storage management at same time, especially for homelab users. Because TrueNAS’s built-in VM management is difficult to use, offers limited VM options, and doesn’t allow for system setting modifications. While it uses Incus as its VM backend after 25.04, the TrueNAS WebUI and middleware still need a lot of work.
And vGPU has become a prevalent method for enabling VMs on Proxmox VE to utilize a GPU card for video encoding and decoding. However, TrueNAS, when run as a VM guest, defaults to using standard Nvidia drivers, not the specialized Grid drivers (NVIDIA’s commercial drivers). Consequently, while some Tesla cards might correctly load nvidia-smi
, they often can’t actually use their encoding and decoding units with these default drivers.
The good news is that starting with TrueNAS SCALE 24.10+, TrueNAS now uses systemd-sysext
to load Nvidia drivers that are pre-packaged during compilation. This extension can load when needed, Which also means you can overwrite and install any custom driver without using install-dev-tools
, avoiding system modifications. Additionally, recovery is quick after TrueNAS updates.
How It Works
The core principle behind the packaged extension source code is quite straightforward:
- It compares the contents of
self.chroot
andself.chroot_base
directories. - It extracts any new or modified files.
- Only files under
usr/
(excludingusr/src/
) are kept; others are deleted. - It cleans up
self.chroot
by removing files and empty directories that don’t belong to the extension. - An
extension-release.d
file is added to identify the extension. - Finally,
self.chroot
is packaged into a SquashFS file and saved todst_path
.
Here’s the Python code:
def build_extension(self, name, dst_path): changed_files = [ os.path.relpath(filename, self.chroot) for filename in map( lambda filename: os.path.join(os.getcwd(), filename), run( ["rsync", "-avn", "--out-format=%f", f"{self.chroot}/", f"{self.chroot_base}/"], log=False, ).stdout.split("\n") ) if os.path.abspath(filename).startswith(os.path.abspath(self.chroot)) ]
sysext_files = [f for f in changed_files if f.startswith("usr/") and not (f.startswith("usr/src/"))]
for root, dirs, files in os.walk(self.chroot, topdown=False): for f in files: path = os.path.relpath(os.path.abspath(os.path.join(root, f)), self.chroot) if path not in sysext_files: os.unlink(os.path.join(root, f))
for d in dirs: try: os.rmdir(os.path.join(root, d)) except NotADirectoryError: os.unlink(os.path.join(root, d)) # It's a symlink except OSError as e: if e.errno == errno.ENOTEMPTY: pass else: raise
os.makedirs(f"{self.chroot}/usr/lib/extension-release.d", exist_ok=True) with open(f"{self.chroot}/usr/lib/extension-release.d/extension-release.{name}", "w") as f: f.write("ID=_any\n")
run(["mksquashfs", self.chroot, dst_path, "-comp", "xz"])
Specifically for the Nvidia driver, this is the relevant part:
def download_nvidia_driver(self): prefix = "https://us.download.nvidia.com/XFree86/Linux-x86_64"
version = get_manifest()["extensions"]["nvidia"]["current"] filename = f"NVIDIA-Linux-x86_64-{version}-no-compat32.run" result = f"{self.chroot}/{filename}"
self.run(["wget", "-c", "-O", f"/{filename}", f"{prefix}/{version}/{filename}"])
os.chmod(result, 0o755) return result
def install_nvidia_driver(self, kernel_version): driver = self.download_nvidia_driver()
self.run([f"/{os.path.basename(driver)}", "--skip-module-load", "--silent", f"--kernel-name={kernel_version}", "--allow-installation-with-running-driver", "--no-rebuild-initramfs"])
os.unlink(driver)
So, modifying this is actually quite simple:
- Replace the download link.
- Adjust the execution options (Grid drivers have different options).
PS: When choosing drivers, pay attention to kernel compatibility. For instance, with the current 25.04 version (kernel version 6.12.15), if you’re using a 16.x driver (the last usable driver for Tesla P4), your grid driver version needs to be greater than 16.9.
Here’s how the modified code would look:
def download_nvidia_driver(self): # i don't where i can find the grid driver prefix = "<put your grid download url without filename in here>" #useless #version = get_manifest()["extensions"]["nvidia"]["current"] filename = f"<filename of your grid driver>" result = f"{self.chroot}/{filename}"
self.run(["wget", "-c", "-O", f"/{filename}", f"{prefix}/{filename}"])
os.chmod(result, 0o755) return result
def install_nvidia_driver(self, kernel_version): driver = self.download_nvidia_driver() # fix option self.run([f"/{os.path.basename(driver)}", "--silent", f"--kernel-name={kernel_version}"])
os.unlink(driver)
Building the Extension
In here, I’ll use 25.04.0
as an example.
The official documentation already provides some hints, but here are some key things to note:
Install Dependencies
(Nothing need to notice)
sudo apt install build-essential debootstrap git python3-pip python3-venv squashfs-tools unzip libjson-perl rsync libarchive-tools
Select the Tags Branch
Use the tag branch instead of release/xxxxx
# tag name is TS-<version>git clone -b TS-25.04.0 https://github.com/truenas/scale-build.git
Configure Environment Variables
The code reads environment variables to determine the compiled “version” and “Train”. So, if you don’t specify them, you’ll only be able to compile the Master train.
export TRUENAS_TRAIN="TrueNAS-SCALE-Fangtooth"export TRUENAS_VERSION="25.04.0"
Start the Build
make checkout
make packages
make update
Once the build is complete, you’ll need to mount the compiled rootfs.squashfs
and extract its contents.
mkdir -p ./tmpfile/rootfsmount ./tmp/update/rootfs.squashfs ./tmpfile/rootfs
At this point, your nvidia.raw
extension should be available:
ls -al ./tmpfile/rootfs/usr/share/truenas/sysext-extensions/nvidia.raw
Overwriting the Existing Driver
You’ll need to replace the nvidia.raw
file on your running TrueNAS system at /usr/share/truenas/sysext-extensions/nvidia.raw
with the one you just compiled.
First, you need to make the /usr
dataset writable:
zfs set readonly=off boot-pool/ROOT/<system version>/usr# For 25.04.0, it would be:zfs set readonly=off boot-pool/ROOT/25.04.0/usr
PS: If you’ve previously enabled the Nvidia driver via the WebUI, you might need to run systemd-sysext unmerge
first. This is because systemd-sysext
will set /usr
as read-only.
Overwrite it!
cp /the-path-you-upload/nvidia.raw /usr/share/truenas/sysext-extensions/nvidia.raw
After you’ve copied the file, simply run:
systemd-sysext merge# Don't forget to restart docker servicesystemctl restart docker
And, It should work.
nvidia-smi