The ZFS Boot Chain — DKMS, Dracut, and the initramfs.
This is the complete order of operations for how a ZFS-on-root Linux system gets built and boots. Every step, what it does, why it matters. This is what kldload automates — and what you need to understand if you want to do it yourself or debug a boot failure.
Phase 1: Build Time (inside the installer)
Step 1: Install kernel packages
dnf install kernel kernel-core kernel-modules kernel-devel
Installs the Linux kernel binary (vmlinuz) and the development headers.
kernel-devel provides the source headers needed to compile out-of-tree modules like ZFS.
The kernel lands at /boot/vmlinuz-5.14.0-xxx.el9.x86_64.
Step 2: Install ZFS + DKMS
dnf install zfs zfs-dkms dkms gcc make
zfs — userspace tools (zpool, zfs commands).
zfs-dkms — ZFS kernel module source code (not compiled yet).
dkms — Dynamic Kernel Module Support framework.
gcc, make — compiler toolchain to build the module.
Step 3: DKMS builds the ZFS kernel module
dkms build -m zfs -v 2.2.9 -k 5.14.0-687.el9.x86_64
dkms install -m zfs -v 2.2.9 -k 5.14.0-687.el9.x86_64
depmod -a 5.14.0-687.el9.x86_64
DKMS takes the ZFS source from /usr/src/zfs-2.2.9/,
compiles it against the installed kernel headers,
and produces zfs.ko, spl.ko, etc. — the actual kernel modules.
They're installed to /usr/lib/modules/5.14.0-xxx/extra/zfs/.
depmod updates the module dependency map so the kernel can find them.
Step 4: Install zfs-dracut
chroot /target dnf install -y zfs-dracut
Installs the dracut module files to /usr/lib/dracut/modules.d/90zfs/.
This directory contains scripts that teach dracut how to handle ZFS:
root=zfs:pool/dataset from the kernel command lineStep 5: Rebuild the initramfs
dracut --force --add "zfs" --kver 5.14.0-687.el9.x86_64
Dracut builds a compressed cpio archive — the initramfs.
--add "zfs" tells dracut to include the 90zfs module.
Dracut's module-setup.sh runs and says "I need these things":
zfs.ko,spl.kokernel modules (from the DKMS build)zpool,zfs,mount.zfsbinaries/etc/hostid(ZFS uses this to identify which pools belong to this machine)- systemd units for pool import and dataset mounting
All packed into /boot/initramfs-5.14.0-xxx.el9.x86_64.img.
Step 6: Install ZFSBootMenu to EFI
cp zfsbootmenu.EFI /boot/efi/EFI/zbm/BOOTX64.EFI
efibootmgr -c -d /dev/vda -p 1 -L "ZFSBootMenu" -l '\EFI\zbm\BOOTX64.EFI'
ZFSBootMenu is a standalone EFI binary — a bootloader that understands ZFS. It scans pools, finds boot environments, and shows a menu. Registered with UEFI firmware so it runs on power-on.
Phase 2: Boot Time (what happens when you power on)
Step 1: UEFI firmware
CPU initializes. Firmware reads the EFI boot entries from NVRAM.
Finds "ZFSBootMenu" → loads /boot/efi/EFI/zbm/BOOTX64.EFI into memory and executes it.
Step 2: ZFSBootMenu runs
Imports rpool (reads zpool.cache or scans all devices).
Finds all datasets under rpool/ROOT/ — these are boot environments.
Shows a menu (or auto-boots the default after timeout).
Reads the selected BE's /boot/vmlinuz-* and /boot/initramfs-*.
Uses kexec to load the kernel + initramfs into memory and jumps to it.
Step 3: Linux kernel starts
Kernel decompresses and initializes. Sets up memory management, CPU scheduling, interrupts.
Mounts the initramfs as the temporary root filesystem (/).
Starts systemd (PID 1) inside the initramfs.
Step 4: Initramfs — dracut's ZFS module runs
This is where the magic happens (or where it fails):
- systemd reads kernel command line:
root=zfs:rpool/ROOT/kldload-node parse-zfs.shintercepts: "I seeroot=zfs:— I know how to handle this"mount-zfs.shruns: loadszfs.koviamodprobe zfs- Reads
/etc/hostidto identify this machine - Imports the pool:
zpool import -N rpool - Mounts root:
mount -t zfs rpool/ROOT/kldload-node /sysroot
Step 5: Switch root
Initramfs has done its job — real root is mounted at /sysroot.
systemd does switch_root /sysroot — pivots from initramfs to the real filesystem.
Initramfs is freed from memory. systemd on the real root takes over as PID 1.
Step 6: Real systemd boots
Step 7: Login prompt
You're booted. ZFS on root. Every dataset mounted. Snapshots running on schedule. Ready to work.
The failure point
Without zfs-dracut:
Kernel boots → dracut runs → sees root=zfs:... →
NO MODULE CLAIMS IT → "FATAL: Don't know how to handle" → halt
With zfs-dracut:
Kernel boots → dracut runs → sees root=zfs:... →
90zfs/parse-zfs.sh claims it → loads zfs.ko → imports pool →
mounts root → switch_root → boot completes
One package. That's the difference between a working system and a blinking cursor.
Debian vs CentOS: different tools, same result
| Step | Debian | CentOS / RHEL |
|---|---|---|
| Initramfs tool | initramfs-tools | dracut |
| ZFS initramfs package | zfs-initramfs | zfs-dracut |
| Rebuild command | update-initramfs -c -k all | dracut --force --add "zfs" --kver $kver |
| Module location | /usr/share/initramfs-tools/ | /usr/lib/dracut/modules.d/90zfs/ |
| Kernel module build | DKMS (same) | DKMS (same) |
| Bootloader | ZFSBootMenu (same) | ZFSBootMenu (same) |
update-initramfs for Debian, dracut for CentOS/RHEL.
Same result: an initramfs with ZFS support baked in, ready to boot.