
A lot of folks are opting to run TrueNAS on Proxmox VE these days. Why? Well, TrueNAS’s built-in VM management can be a bit clunky – even with Incus, the underlying tech isn’t quite seamless, and you often hit snags with CLI commands acting up in the Instance UI. To get around this and supercharge things like RDP desktops or video encoding, many are cleverly using vGPU passthrough to share a single GPU across several virtual machines.
However, TrueNAS by default uses the standard Nvidia drivers, not the specialized Grid drivers (NVIDIA’s commercial drivers). While some Tesla cards might correctly load nvidia-smi
, they often can’t actually use their encoding and decoding units with the 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. These extensions are only loaded when needed.
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
to extract its contents. Here’s how:
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
otherwise controls /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 dockersystemctl restart docker
And, It should work.
nvidia-smi