Just Enough Void
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
initsystem alternative(s) tosystemd. - 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.
- 1. Start Here
- 2. Configure the Live Environment
- 3. Prepare the DISK
- 4. Configure the ZFS Pool
- 5. Installation
- 6. Configure the System
- 7. ZFSBootMenu
- 8. Finish Up
- 9. Resources
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
espserves as the EFI system partition and is formatted with thefat32file system. - Partition
swapprovidesswapspace with encryption courtesy ofdm-crypt. - Partition
poolcontainsrootand is formatted with thezfsfile system using native encryption.
A few assumptions:
- Target device is
x86_64architecture using UEFI to boot. - Use
glibcas 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.isoPrepare 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/sdx2. 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 colemakVerify 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.orgRemote 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_DEVICENVMe
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_DEVICEWipe 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 $DISKPartition 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:
| Number | Size | Code | Format | Use as | Mountpoint |
|---|---|---|---|---|---|
| 1 | 4g | ef00 | vfat | EFI system partition | /efi |
| 2 | 8g | 8200 | swap | Swap partition | (not applicable) |
| 3 | ->END | bf00 | zfs | ZFS 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 $DISK4. 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.keyCreate 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 IDCreate 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/dataExport and re-import pool for installation
zpool export zroot
zpool import -N -R /mnt zroot
zfs load-key -L prompt zrootMount 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 trigger5. Installation
Install the base-system:
XBPS_ARCH=x86_64 xbps-install -S -R https://repo-default.voidlinux.org/current -r /mnt base-system6. Configure the System
Generate the fstab
xgenfstab /mnt > /mnt/etc/fstab && cat /mnt/etc/fstabGenerate the hostid
Generate hostid hexadecimal identifier for use by ZFSBootMenu (0x00bab10c is the default value used internally):
zgenhostid -f 0x00bab10c && hostidEnter 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 /mntSet the root password
passwdCreate 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/sudoersAdd package repository
Add the nonfree subrepo (required for proprietary firmware) and sync mirrors:
xbps-install void-repo-nonfree && xbps-install -S && xbps-query -LInstall 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 zfsSet 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 && dateSet 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.confAssign 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/hostnameEnable 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/fstabRegenerate 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 -fa7. 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}" zrootFormat 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 /efiInstall 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.EFIConfigure 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
cdUnmount and export
Unmount:
umount /mnt/home && umount /mnt/data && umount /mnt/efi && umount -l -n -R /mnt
Export the zpool:
zpool export zrootReboot
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
- ZFSBootMenu: Void Linux Guide
- Void Linux Handbook: Full Disk Encryption and Root on ZFS
- hrmpf rescue system: github.com/leahneukirchen/hrmpf
- OpenZFS Man Pages: zpoolprops.7 and zfsprops.7
- Practical ZFS: Linux home directory on ZFS
- Arch Linux Wiki: Swap encryption
Next: Void Linux: After the First Boot (TODO)
You can like, share, or comment on this post on the Fediverse 💬
« Previous: FreeBSD: After the First Boot