$linuxjunkies
>

ZFS on Root on Debian (deep dive)

Install Debian 12 with ZFS on root: partition layout, boot pool vs root pool, dataset hierarchy, GRUB configuration, and first-boot verification.

AdvancedUbuntuDebianFedoraArch14 min readUpdated June 1, 2026

Before you start

  • Debian 12 live ISO written to USB and booted on the target machine
  • UEFI firmware with Secure Boot disabled or MOK key enrolled
  • Target disk(s) identified and confirmed expendable — all data will be destroyed
  • Network connectivity from the live session for package downloads

Installing Debian with ZFS as the root filesystem is not a checkbox in the standard installer — it requires manual partitioning, building a boot environment from a live session, and understanding how ZFS pools, datasets, and GRUB interact. This guide follows the OpenZFS-on-Linux approach that the Debian wiki recommends, adapted for Debian 12 (Bookworm). Expect to spend 60–90 minutes the first time.

Why ZFS on Root?

ZFS gives you copy-on-write snapshots, built-in checksumming, dataset-level quotas, and trivial boot environment cloning. The tradeoff: the setup is complex, ZFS is not in the mainline kernel (it ships via DKMS on Debian), and pool/dataset layout decisions made at install time are painful to undo. Get the layout right from the start.

Concepts to Understand First

Boot pool vs. root pool

GRUB can read ZFS, but only a limited subset of ZFS features. If your root pool uses features GRUB does not understand, the system will not boot. The standard solution is two pools:

  • bpool — a small pool (<2 GB) using only features GRUB supports. Holds /boot.
  • rpool — the main pool with all modern features enabled. Holds everything else.

This split is not optional if you want ZFS compression, encryption, or features introduced after GRUB's ZFS support froze. The bpool must be created with -d (disable all features) plus explicit -o feature@async_destroy=enabled and a short allow-list.

Dataset hierarchy

ZFS root installs work best with a structured dataset tree rather than one flat dataset. A typical layout:

  • rpool/ROOT/debian — mounted at /
  • rpool/home — mounted at /home
  • rpool/var/log, rpool/var/cache — separate so snapshots of ROOT do not capture ephemeral data
  • bpool/BOOT/debian — mounted at /boot

Prerequisites

  • Debian 12 live ISO (the standard or netinst live image works; avoid the minimal non-live installer)
  • Target disk(s) identified — this guide uses /dev/sda for a single-disk setup and notes where to mirror
  • UEFI firmware (GPT + EFI partition); the guide notes BIOS/MBR differences where relevant
  • Network access from the live environment for package installation

Step 1: Prepare the Live Environment

Boot the Debian live ISO. Open a terminal and gain root:

sudo -i

Install the ZFS utilities and DKMS module into the live session:

apt update
apt install --yes debootstrap gdisk zfsutils-linux

Confirm the ZFS module loaded:

modprobe zfs
lsmod | grep zfs

Step 2: Partition the Disk

Wipe the target disk and create a GPT partition table. Adjust sizes to your disk. This layout assumes UEFI:

DISK=/dev/sda
sgdisk --zap-all $DISK
sgdisk -n 1:0:+1G   -t 1:EF00 -c 1:"EFI"  $DISK   # EFI System Partition
sgdisk -n 2:0:+2G   -t 2:BE00 -c 2:"bpool" $DISK  # ZFS boot pool
sgdisk -n 3:0:0     -t 3:BF00 -c 3:"rpool" $DISK  # ZFS root pool
partprobe $DISK

For BIOS/MBR systems, replace the EFI partition with a 1 MB BIOS boot partition (type EF02) and adjust GRUB installation later.

Step 3: Create the ZFS Pools

Boot pool (bpool)

The -d flag disables all features; only those explicitly listed are enabled — keeping the pool within GRUB's understanding:

zpool create \
  -o ashift=12 \
  -o autotrim=on \
  -d \
  -o feature@async_destroy=enabled \
  -o feature@bookmarks=enabled \
  -o feature@embedded_data=enabled \
  -o feature@empty_bpobj=enabled \
  -o feature@enabled_txg=enabled \
  -o feature@extensible_dataset=enabled \
  -o feature@filesystem_limits=enabled \
  -o feature@hole_birth=enabled \
  -o feature@large_blocks=enabled \
  -o feature@lz4_compress=enabled \
  -o feature@spacemap_histogram=enabled \
  -O acltype=posixacl \
  -O canmount=off \
  -O compression=lz4 \
  -O devices=off \
  -O normalization=formD \
  -O relatime=on \
  -O xattr=sa \
  -O mountpoint=/boot \
  -R /mnt \
  bpool ${DISK}2

Root pool (rpool)

The root pool gets all modern features. Add -O encryption=aes-256-gcm -O keylocation=prompt -O keyformat=passphrase before -O mountpoint=/ if you want native ZFS encryption:

zpool create \
  -o ashift=12 \
  -o autotrim=on \
  -O acltype=posixacl \
  -O canmount=off \
  -O compression=lz4 \
  -O dnodesize=auto \
  -O normalization=formD \
  -O relatime=on \
  -O xattr=sa \
  -O mountpoint=/ \
  -R /mnt \
  rpool ${DISK}3

For a mirrored pair, replace ${DISK}3 with mirror ${DISK}3 /dev/sdb3 (mirror both pools similarly).

Step 4: Create Datasets

# Root pool containers
zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o canmount=noauto -o mountpoint=/ rpool/ROOT/debian
zpool set bootfs=rpool/ROOT/debian rpool

# Boot pool
zfs create -o canmount=off -o mountpoint=none bpool/BOOT
zfs create -o canmount=noauto -o mountpoint=/boot bpool/BOOT/debian

# Mount root and boot
zfs mount rpool/ROOT/debian
zfs mount bpool/BOOT/debian

# Additional datasets
zfs create -o mountpoint=/home rpool/home
zfs create -o mountpoint=/root rpool/home/root
chmod 700 /mnt/root
zfs create -o canmount=off -o mountpoint=/var rpool/var
zfs create -o canmount=off rpool/var/lib
zfs create rpool/var/log
zfs create rpool/var/cache
zfs create rpool/var/spool
zfs create -o com.sun:auto-snapshot=false rpool/var/cache
zfs create rpool/tmp
chmod 1777 /mnt/tmp

Step 5: Install Debian with debootstrap

debootstrap bookworm /mnt

# Write pool cache so the initramfs can import pools
mkdir -p /mnt/etc/zfs
zpool set cachefile=/etc/zfs/zpool.cache bpool
zpool set cachefile=/etc/zfs/zpool.cache rpool
cp /etc/zfs/zpool.cache /mnt/etc/zfs/

Bind-mount the virtual filesystems and chroot:

mount --make-private --rbind /dev  /mnt/dev
mount --make-private --rbind /proc /mnt/proc
mount --make-private --rbind /sys  /mnt/sys
chroot /mnt /usr/bin/env \
  DISK=$DISK \
  bash --login

Step 6: Configure the Chroot Environment

# Hostname and basic config
echo "myhostname" > /etc/hostname
echo "127.0.1.1 myhostname" >> /etc/hosts

# /etc/fstab — EFI only; ZFS mounts itself
cat > /etc/fstab <<'EOF'
#           
tmpfs   /tmp   tmpfs  defaults,nosuid,nodev  0  0
EOF

# Locales and timezone
apt install --yes locales
dpkg-reconfigure locales
dpkg-reconfigure tzdata

# Set root password
passwd

Add the contrib component (required for ZFS DKMS on Debian) and install the kernel and ZFS packages:

apt install --yes software-properties-common
apt-add-repository contrib
apt update
apt install --yes linux-image-amd64 linux-headers-amd64 \
  zfs-initramfs zfs-dkms dosfstools

Step 7: Install and Configure GRUB

Format and mount the EFI partition, then install GRUB:

mkdosfs -F 32 -s 1 -n EFI ${DISK}1
mkdir -p /boot/efi
echo "${DISK}1  /boot/efi  vfat  umask=0022,fmask=0133  0  0" >> /etc/fstab
mount /boot/efi

apt install --yes grub-efi-amd64 grub-efi-amd64-signed shim-signed

Set GRUB to recognize ZFS and update configuration:

echo 'GRUB_CMDLINE_LINUX="root=ZFS=rpool/ROOT/debian"' >> /etc/default/grub
update-grub
grub-install --target=x86_64-efi --efi-directory=/boot/efi \
  --bootloader-id=debian --recheck --no-floppy

For BIOS systems, use grub-install ${DISK} with the grub-pc package instead.

Step 8: Finalize and First Boot

# Snapshot clean install state
zfs snapshot bpool/BOOT/debian@install
zfs snapshot rpool/ROOT/debian@install

# Enable ZFS services
systemctl enable zfs-import-cache zfs-import.target zfs-mount zfs-share zfs-zed

# Exit chroot, unmount, export pools
exit
mount | grep -v zfs | tac | awk '/\/mnt/{print $3}' | xargs -I{} umount -lf {}
zpool export -a

Remove the live ISO and reboot. GRUB should appear and boot into Debian from rpool/ROOT/debian.

Verification

After the first successful boot, confirm both pools are imported and healthy:

zpool status
zpool list
zfs list -t all

Output will vary by hardware, but HEALTH should read ONLINE for both bpool and rpool. Verify the boot dataset:

zpool get bootfs rpool

Troubleshooting

GRUB drops to rescue shell

This almost always means GRUB cannot find or read the ZFS pool. Boot the live ISO, import the pools (zpool import -f -R /mnt rpool bpool), chroot back in, and run update-grub again. Double-check that bpool was created with -d and only the allowed feature set.

initramfs cannot import rpool

The zpool.cache file is missing or stale in the initramfs. Inside the chroot run zpool set cachefile=/etc/zfs/zpool.cache rpool bpool, then update-initramfs -u -k all.

ZFS DKMS module fails to build

Ensure linux-headers-amd64 matches your running kernel version exactly. Run dkms status to see the current state and dkms build zfs/$(dkms status | grep zfs | awk '{print $2}') to retry manually.

Pool imported read-only on boot

Usually caused by an unclean export. Boot the live ISO, run zpool import -f rpool, export cleanly with zpool export rpool, and reboot.

tested on:Debian 12 (Bookworm)

Frequently asked questions

Can I use native ZFS encryption on the root pool?
Yes. Add -O encryption=aes-256-gcm -O keylocation=prompt -O keyformat=passphrase when creating rpool. The bpool should remain unencrypted so GRUB can read /boot without a pre-boot unlock mechanism.
Why does bpool need a restricted feature set?
GRUB's ZFS implementation is frozen at a point that predates many modern ZFS features. If bpool uses features GRUB cannot parse, GRUB cannot read the filesystem and will drop to a rescue shell.
Can I add a second disk for mirroring later?
Yes, but it is far easier to set up mirrors at install time. To add a mirror vdev later, use zpool attach rpool /dev/sda3 /dev/sdb3 (and the same for bpool). Resilvering takes time proportional to used space.
How do I create and boot a new boot environment after a major upgrade?
Snapshot rpool/ROOT/debian, clone it to rpool/ROOT/debian-new, update /etc/grub.d or use a tool like zfsbootmenu, run update-grub, and reboot. If the new environment is broken, simply set bootfs back to the previous dataset.
Does this setup work with Secure Boot?
The grub-efi-amd64-signed and shim-signed packages included in Step 7 support Secure Boot. However, the ZFS DKMS module is not signed by default, which can cause failures with strict Secure Boot MOK policies. You must either enroll your own MOK key or disable Secure Boot.

Related guides