| your Linux construction kit
Source
← Back to ZFS Overview

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.

Why DKMS? ZFS can't be in the kernel tree (license). So it ships as source and gets compiled locally against your exact kernel. When you upgrade kernels later, DKMS automatically rebuilds ZFS for the new kernel.

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:

module-setup.sh
Tells dracut what binaries and kernel modules to pack into the initramfs
parse-zfs.sh
Parses root=zfs:pool/dataset from the kernel command line
mount-zfs.sh
Imports the pool and mounts the root dataset
zfs-generator.sh
Creates systemd mount units dynamically at boot
zfs-lib.sh
Shared helper functions for pool import and key loading
This is the step that was failing on CentOS. Without these files, dracut doesn't know ZFS exists. The kernel boots, sees root=zfs:..., and nobody claims it. Fatal.

Step 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.ko kernel modules (from the DKMS build)
  • zpool, zfs, mount.zfs binaries
  • /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.

The initramfs is a tiny Linux filesystem loaded into RAM at boot. It contains just enough to get from "kernel loaded" to "real root mounted." Think of it as a go-bag with everything you need to get from the front door to the bedroom in the dark.

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.

The building superintendent unlocks the front door and lets in whoever's on the list.

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.

The elevator. It knows about every floor and lets you pick which one.

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):

  1. systemd reads kernel command line: root=zfs:rpool/ROOT/kldload-node
  2. parse-zfs.sh intercepts: "I see root=zfs: — I know how to handle this"
  3. mount-zfs.sh runs: loads zfs.ko via modprobe zfs
  4. Reads /etc/hostid to identify this machine
  5. Imports the pool: zpool import -N rpool
  6. Mounts root: mount -t zfs rpool/ROOT/kldload-node /sysroot
Without zfs-dracut installed, step 2 never happens. No module claims root=zfs:, dracut says "FATAL: Don't know how to handle", and the system halts. One missing package = unbootable system.

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.

The scaffolding comes down. The building is standing on its own.

Step 6: Real systemd boots

zfs-import-cache.service
Ensures rpool is imported (idempotent — already done by initramfs)
zfs-mount.service
Mounts all datasets: /home, /var, /srv, /var/log, etc.
zfs-zed.service
ZFS Event Daemon — watches for errors, scrub results, state changes
NetworkManager, sshd, kweb...
Everything else — networking, SSH, web UI, your applications

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

StepDebianCentOS / RHEL
Initramfs toolinitramfs-toolsdracut
ZFS initramfs packagezfs-initramfszfs-dracut
Rebuild commandupdate-initramfs -c -k alldracut --force --add "zfs" --kver $kver
Module location/usr/share/initramfs-tools//usr/lib/dracut/modules.d/90zfs/
Kernel module buildDKMS (same)DKMS (same)
BootloaderZFSBootMenu (same)ZFSBootMenu (same)
kldload handles both paths automatically. The installer detects the distro and uses the right initramfs tool. update-initramfs for Debian, dracut for CentOS/RHEL. Same result: an initramfs with ZFS support baked in, ready to boot.