Just Enough Void

Last edited on 2026-06-29 Tagged under  #void   #linux   #zfs   #encrypt   #zbm 

I've only used it for a few weeks, however I've quickly come to love Void Linux.

Void meets all the criteria I especially value when evaluating a desktop Linux distro for my personal use:

  • Built and led by a community and built from scratch (not a fork).
  • A rolling release model of continuous, incremental upgrades.
  • Provide officially supported init system alternative(s) to systemd.
  • Decent project documentation to get started and perform basic tasks.
  • Tinkering friendly.

All this rolling and tinkering requires a robust means of recovery when things go awry: OpenZFS for snapshots of system states, combined with ZFSBootMenu for booting and rollbacks to former, better times.

Working my way through this excellent installation guide, I show the choices I make to create an encrypted Root-On-ZFS minimal Linux system with "just enough" to provide a solid foundation to build upon further: whether that be setting up a desktop, laptop, or server.

ascii kitty

1. Start Here

Throughout this HOWTO, if you see square brackets [] in code blocks, that means the word of code (square brackets included) should be replaced with something else. This is detailed in the instructions before or after the code block.

Void Linux is installed as the sole operating system on a single disk using a three-partition layout:

  • Partition esp serves as the EFI system partition and is formatted with the fat32 file system.
  • Partition swap provides swap space with encryption courtesy of dm-crypt.
  • Partition pool contains root and is formatted with the zfs file system using native encryption.

A few assumptions:

  • Target device is x86_64 architecture using UEFI to boot.
  • Use glibc as system libc.
  • Secure boot is disabled on target device.
  • Installation image is prepared on a Linux/BSD system.
  • Network access during install uses a wired interface.
  • System does not require hibernation support.

Acquire an installation image

The standard Void live images do not include support for the ZFS file system. A Void team member maintains the hrmpf rescue system. These prebuilt images are extensions of the Void images that include precompiled ZFS modules in addition to other useful tools.

From the releases directory, download hrmpf-x86_64-[RELEASE].iso.

As of July 1 2026, [RELEASE] is 20251231.

The SHA256 strings are printed in the directory. Verify the image integrity:

sha256sum hrmpf-x86_64-20251231.iso

Prepare the USB installation media

Write the installer to an unmounted USB storage device running the dd command as root.

ALERT
Be very careful to note the proper device (which can be identified with the lsblk command). All contents on the device will be lost!

Example: On a Linux system, if a USB stick appears as sdx1, then write the installer to sdx (omit partition number):

dd bs=4M conv=fsync oflag=direct status=progress \
  if=hrmpf-x86_64-20251231.iso of=/dev/sdx

2. Configure the Live Environment

Boot the target device from the hrmpf installation media. User is automatically logged in as user root to the first virtual console.

Set larger console font

Console fonts are located in /usr/share/kbd/consolefonts/ and a different font can be set with setfont, omitting the path and file extension.

If the existing font size appears too small, running:

setfont -d

... will double the size.

Set temporary console keyboard

Default console keymap is us. Available keymaps are listed in /usr/share/kbd/keymaps/.

If some other keymap is desired, set a different keymap temporarily with loadkeys:

loadkeys [keymap]

... where [keymap] is the desired keyboard layout.

Example: I configure the system to use my preferred colemak layout:

loadkeys colemak

Verify network connectivity

Wired network interfaces should be auto-enabled and connected at boot.

Verify the network interface is active, has been assigned an address, and the internet is reachable:

ip addr
ping -c 3 8.8.8.8
ping -c 3 voidlinux.org

Remote login to installer

Make this manual installation process easier (i.e. cut-n-paste commands) by remotely logging into the installer via ssh from another computer.

The sshd daemon is auto-started at boot. Switch to the other computer and ssh into the target device as anon:voidlinux:

ssh anon@[ip_address]

... where [ip_address] is the target device’s address obtained with the ip addr command above.

Switch to root (password voidlinux):

su -

Verify boot mode

Confirm target device is using UEFI boot mode:

cat /sys/firmware/efi/fw_platform_size

If the command returns 64, then system is booted in UEFI with 64-bit x64 UEFI and we are good to go.

ALERT
If the file does not exist, the device is not using UEFI and these instructions will not produce a working system.

3. Prepare the DISK

Set up a custom partition layout on a single disk before implementing the Void base installation.

Define DISK variables

Identify the disk where Void will be installed by listing block devices:

lsblk -f

Set DISK variables for either a SATA or NVMe disk:

SATA

Example disk: sda

export DISK="/dev/sda"
export ESP_PART="1"
export SWAP_PART="2"
export POOL_PART="3"
export ESP_DEVICE="${DISK}${ESP_PART}"
export SWAP_DEVICE="${DISK}${SWAP_PART}"
export POOL_DEVICE="${DISK}${POOL_PART}"
echo $ESP_DEVICE && echo $SWAP_DEVICE && echo $POOL_DEVICE

NVMe

Example disk: nvme0n1

export DISK="/dev/nvme0n1"
export ESP_PART="1"
export SWAP_PART="2"
export POOL_PART="3"
export ESP_DEVICE="${DISK}p${ESP_PART}"
export SWAP_DEVICE="${DISK}p${SWAP_PART}"
export POOL_DEVICE="${DISK}p${POOL_PART}"
echo $ESP_DEVICE && echo $SWAP_DEVICE && echo $POOL_DEVICE

Wipe the DISK

If there was previously a ZFS pool on DISK, run:

zpool labelclear -f $DISK

If DISK was previously configured with LVM, bring down the volume group:

vgchange -an

Wipe existing file systems and partition table on DISK:

wipefs -af $DISK && sgdisk --zap-all --clear $DISK

Notify the system of changes to the partition table:

partprobe $DISK

Partition the DISK

NOTE
Many partitioning guides assign 256-512M of space to the EFI system partition. I like to future-proof the partition for whatever else Linux might want to store there by assigning a more generous 4G of space.

Create a GPT partition table on DISK with the following layout:

NumberSizeCodeFormatUse asMountpoint
14gef00vfatEFI system partition/efi
28g8200swapSwap partition(not applicable)
3->ENDbf00zfsZFS pool partition/

Create the EFI system partition:

sgdisk -n "${ESP_PART}:1m:+4g" -t "${ESP_PART}:ef00" -c 0:esp $DISK

Create the swap partition:

sgdisk -n "${SWAP_PART}:0:+8g" -t "${SWAP_PART}:8200" -c 0:swap $DISK

Create the ZFS pool partition:

sgdisk -n "${POOL_PART}:0:0" -t "${POOL_PART}:bf00" -c 0:pool $DISK

Display DISK layout:

partprobe $DISK && sgdisk -p $DISK

4. Configure the ZFS Pool

When adding disks or partitions to ZFS pools, it is advisable to refer to them by the symbolic links created in /dev/disk/by-partuuid so that ZFS will identify the right devices even if disk naming should change at some point. Using device nodes like /dev/sda2 may cause import failures.

So I create a POOL_ID variable:

POOL_ID=/dev/disk/by-partuuid/$( blkid -s PARTUUID -o value $POOL_DEVICE )

Verify:

ls -al /dev/disk/by-partuuid/ && echo "POOL_ID = $POOL_ID"

Create the encryption keyfile

Store the encryption passphrase for the ZFS pool in a keyfile:

echo 'SuperSecretPassphrase' > /etc/zfs/zroot.key
chmod 000 /etc/zfs/zroot.key

Create the encrypted pool

Create the pool with native encryption enabled:

zpool create -f \
 -o ashift=12 \
 -o autotrim=on \
 -o compatibility=openzfs-2.3-linux \
 -O acltype=posixacl \
 -O xattr=sa \
 -O compression=lz4 \
 -O encryption=aes-256-gcm \
 -O keylocation=file:///etc/zfs/zroot.key \
 -O keyformat=passphrase \
 -O relatime=on \
 -m none zroot "$POOL_ID"

Define ID variable

File os-release defines variables that describe the current operating system. Use the $ID variable to set the short name of the Linux distribution in later commands:

. /etc/os-release && export ID && env | grep ID

Create the datasets

NOTE
It is necessary to explicitly set the canmount=noauto on every boot environment you create.

zfs create -o mountpoint=none zroot/ROOT
zfs create -o mountpoint=/ -o canmount=noauto zroot/ROOT/${ID}

Set the preferred boot file system:

zpool set bootfs=zroot/ROOT/${ID} zroot

I create an additional home dataset for each system:

zfs create zroot/ROOT/${ID}/home

This allows me to keep user config files unique to each boot environment that I might create in the future. It also separates user data from system data, which is useful for ZFS snapshots and enables system rollbacks while leaving user data untouched.

To share data between boot environments, I create a data dataset to store files common to all of them:

zfs create -o mountpoint=/data zroot/data

Export and re-import pool for installation

zpool export zroot
zpool import -N -R /mnt zroot
zfs load-key -L prompt zroot

Mount the datasets

zfs mount zroot/ROOT/${ID}
zfs mount zroot/ROOT/${ID}/home
zfs mount zroot/data

Verify:

# mount -t zfs
zroot/ROOT/void on /mnt type zfs (rw,relatime,xattr,posixacl,casesensitive)
zroot/ROOT/void/home on /mnt/home type zfs (rw,relatime,xattr,posixacl,casesensitive)
zroot/data on /mnt/data type zfs (rw,relatime,xattr,posixacl,casesensitive)

Update device symlinks:

udevadm trigger

5. Installation

Install the base-system:

XBPS_ARCH=x86_64 xbps-install -S -R https://repo-default.voidlinux.org/current -r /mnt base-system

6. Configure the System

Generate the fstab

xgenfstab /mnt > /mnt/etc/fstab && cat /mnt/etc/fstab

Generate the hostid

Generate hostid hexadecimal identifier for use by ZFSBootMenu (0x00bab10c is the default value used internally):

zgenhostid -f 0x00bab10c && hostid

Enter chroot

Copy files into the mounted ZFS file system:

cp /etc/hostid /mnt/etc/
mkdir /mnt/etc/zfs && cp /etc/zfs/zroot.key /mnt/etc/zfs/
mkdir -p /mnt/var/db/xbps/keys && cp /var/db/xbps/keys/* /mnt/var/db/xbps/keys/

Enter the chroot environment:

xchroot /mnt

Set the root password

passwd

Create the superuser

Create a user account with superuser privileges:

useradd -m -G wheel [username]

... where [username] is the desired name for the account.

Set a password for [username]:

passwd [username]

Activate wheel group access for the sudo command:

sed -i "s/# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/" /etc/sudoers

Add package repository

Add the nonfree subrepo (required for proprietary firmware) and sync mirrors:

xbps-install void-repo-nonfree && xbps-install -S && xbps-query -L

Install extra packages

Identify the processor vendor:

grep vendor_id /proc/cpuinfo

Define a variable for an appropriate microcode package to load updates and security fixes:

UCODE="[vendor]"

... where [vendor] for Intel processors is intel-ucode and AMD processors is linux-firmware-amd.

Install:

xbps-install $UCODE cryptsetup curl dkms efibootmgr nano terminus-font zfs

Set console keymap, font, and hardware clock

NOTE
For terminus font settings, see /usr/share/kbd/consolefonts/README.Lat2-Terminus16 for details.

rc.conf is read during boot and configures the system. Open the file:

nano /etc/rc.conf

Set the KEYMAP, FONT, and HARDWARECLOCK.

Example:

HARDWARECLOCK="UTC"
KEYMAP="us"
FONT="ter-u24b"

Save changes and exit.

Set the timezone

Timezones are located in /usr/share/zoneinfo/[Region]/[City], where [Region] is the geographical region (Africa, America, Europe, ...) and the [City] within that region.

Example: Create the localtime symbolic link to the timezone where Toronto is located:

ln -sf /usr/share/zoneinfo/America/Toronto /etc/localtime
hwclock --systohc && date

Set the locale

Open the libc-locales file:

nano /etc/default/libc-locales

Uncomment the desired system locales (example: en_CA and en_US):

en_CA.UTF-8 UTF-8
en_US.UTF-8 UTF-8

Save changes and exit.

Generate the locales:

xbps-reconfigure -f glibc-locales

Set the default system locale:

echo "LANG=en_CA.UTF-8" > /etc/locale.conf

Assign the hostname

Create the hostname file:

echo [hostname] > /etc/hostname

... where [hostname] is the desired name of the system (single word, no spaces):

echo voidlinux > /etc/hostname

Enable services

Wired networks can be configured statically or dynamically with dhcpcd.

Enable the service:

ln -s /etc/sv/dhcpcd /etc/runit/runsvdir/default/

Default activity is for dhcpcd to configure all interfaces with DHCP. Changes are made in /etc/dhcpcd.conf. See dhcpcd.conf(5) for more details.

Enable the sshd service to allow remote logins:

ln -s /etc/sv/sshd /etc/runit/runsvdir/default/

Create encrypted swap

Using raw dm-crypt enables the system to generate a random, one-time encryption key at boot that requires no action from the user to encrypt swap. At shutdown the key is discarded, rendering any remaining data effectively destroyed.

Retrieve the PARTUUID of SWAP_DEVICE:

blkid | grep $SWAP_DEVICE

Configure dm-crypt to set up swap at boot by adding an entry to crypttab:

echo 'swap PARTUUID=[long_string_of_characters] /dev/urandom swap,cipher=aes-xts-plain64,size=512' >> /etc/crypttab

... where [long_string_of_characters] is replaced with the string retrieved by blkid.

Add swap device to fstab:

echo '/dev/mapper/swap  none    swap    defaults    0 0' >> /etc/fstab

Regenerate initramfs

Configure dracut for ZFS boot support by creating the zfs.conf file:

nano /etc/dracut.conf.d/zfs.conf

NOTE
Make sure to enter spaces surrounding the module and item names.

Add:

nofsck="yes"
add_dracutmodules+=" zfs "
omit_dracutmodules+=" btrfs resume "
force_drivers+=" zfs "
install_items+=" /etc/zfs/zroot.key "

Save changes and exit.

Ensure all installed packages are configured properly and a working initramfs is generated:

xbps-reconfigure -fa

7. ZFSBootMenu

Install the ZBM bootloader to support Root-on-ZFS boot environments on Linux.

Set boot properties

NOTE
Add the hibernate=no argument to ensure that no process can trigger a suspend-to-disk action, which is consistent with the goal of using an ephemeral, random-key swap space.

Assign command-line arguments to be used when booting the kernel:

zfs set org.zfsbootmenu:commandline="quiet hibernate=no" zroot/ROOT

Configure key caching:

zfs set org.zfsbootmenu:keysource="zroot/ROOT/${ID}" zroot

Format and mount the ESP partition

NOTE
Labels on file systems are optional, but helpful. They are a more reliable way to identify the correct partition than simple device nodes and allow for easy mounting without a UUID.

Create a fat32 file system on the partition:

mkfs.fat -n ESP -F 32 $ESP_DEVICE

Add the partition to fstab:

echo 'LABEL=ESP     /efi    vfat    defaults    0 0' >> /etc/fstab

Mount:

mkdir /efi && mount $ESP_DEVICE /efi

Install the prebuilt executable

Install a prebuilt ZBM executable to the ESP:

mkdir -p /efi/EFI/ZBM
curl -o /efi/EFI/ZBM/VMLINUZ.EFI -L https://get.zfsbootmenu.org/efi
cp /efi/EFI/ZBM/VMLINUZ.EFI /efi/EFI/ZBM/VMLINUZ-BACKUP.EFI

Configure EFI boot entries

efibootmgr -c -d "$DISK" -p "$ESP_PART" -L "ZFSBootMenu (Backup)" \
  -l '\EFI\ZBM\VMLINUZ-BACKUP.EFI'
efibootmgr -c -d "$DISK" -p "$ESP_PART" -L "ZFSBootMenu" \
  -l '\EFI\ZBM\VMLINUZ.EFI'

8. Finish Up

Exit chroot

exit
cd

Unmount and export

Unmount:

umount /mnt/home && umount /mnt/data && umount /mnt/efi && umount -l -n -R /mnt

Export the zpool:

zpool export zroot

Reboot

reboot

NOTE
When prompted for passphrase to unlock zpool, keymap is us regardless of the keymap that might have been set in /etc/rc.conf.

User is prompted for the passphrase to unlock the encrypted ZFS pool. Upon success, boot resumes...

voidlinux login:

Welcome to Void!

9. Resources

Next: Void Linux: After the First Boot (TODO)

You can like, share, or comment on this post on the Fediverse 💬

Thanks for reading! Read other posts?

« Previous: FreeBSD: After the First Boot