Installing Linux on ZFS
Tags: linux, zfs
Published: 2022-06-05 17:48:47



ZFS is a fascinating filesystem that is packed with features. It is mainly geared towards data storage use cases such as
NAS or Datacenter usage. One feature however intrigued me for workstation usage: The ability to not only take incremental
snapshots, but also transfer those over the network to another ZFS filesystem. This feature could solve a lot of my current
backup woes - so I set out on a journey to install Linux on ZFS.

I quickly learned, that guides about this topic are very cargocult-y, repeating things guides before them have said without
questioning **why** those things were said. This leads to a situation where these guides are needlessly complicated and often
fail to address the few important pieces of information. This is where this series of blog posts starts: My goal is to provide
a **simple** guide to running Linux on ZFS written largely from scratch. I will try to leave no stone unturned and nothing implied
so readers can get a feel for which parts of the guide are important for a running system and which are down to my personal
preferences.

To start things off, let's install archlinux on ZFS. I will be using Arch Linux for these guides as it's installation process (and
its lack of automation) lend itself very well towards this kind of systems exploration: If you have to do everything yourself
you have no choice but to learn how things work.

## 0. Assumptions

This guide makes a couple of assumptions:

1. You area installing this on a UEFI based system. This should be true for all modern PCs but if you are trying this in a virtual machine, you may have to explicitly configure it that way [^uefi]
2. No dual-booting is required
3. We are installing on a 64bit intel based system
4. We are going to use grub
5. We are not using ZFS encryption. In order to use encryption on ANY dataset in the root pool, the `/boot` directory must be located on a different partition similar to LUKS setups. I will create a follow-up article explaining how Linux on encrypted ZFS works.

[^uefi]: BIOS-boot systems should work similarly but without the EFI Partition and with a different `grub-install` command. I haven't tried it though, so I can't vouch for it


## 1. Build a Live ISO that contains ZFS

Like a lot of distributions, archlinux live environments do not ship with ZFS. This is mainly due to the licensing disagreement [^zfs-licensing]
that comes up a lot when talking about ZFS in linux environments. For archlinux, one can use the `archzfs` repository [^archzfs]. In order to
have ZFS included in an archlinux live environment, one has to build their own archiso including the arch packages.

I will skip over this fairly quickly, for more information check out the archwiki page on ZFS [^archiso-zfs], the one on archiso [^archiso] and my
repository containing the building blocks described here [^j6s-archiso-zfs].

[^zfs-licensing]: <https://openzfs.github.io/openzfs-docs/License.html>
[^archzfs]: <https://github.com/archzfs/archzfs/wiki>
[^archiso-zfs]: <https://wiki.archlinux.org/title/ZFS#Create_an_Archiso_image_with_ZFS_support>
[^archiso]: <https://wiki.archlinux.org/title/Archiso>
[^j6s-archiso-zfs]: <https://github.com/j6s/archiso-zfs>


1. Install ``archiso``
2. Create a new archiso profile by copying ``/usr/share/archiso/configs/releng/`` into a directory of your own (e.g. ``./archlive``)
3. Trust the pacman key and add the archzfs repository to the ``pacman.conf`` file in your archiso directory
4. Append ``linux-headers``, ``zfs-dkms``, ``zfs-utils`` to the list of packages that will be installed in the live environment located in ``packages.x86_64``
5. Optional: In order to save yourself the tedious job of manually typing the pacman key later on, create a file containing it in ``airrootfs``. If you are using my repository, then there will be ``/zfs-key.sh`` with a script to add the key and ``/zfs-pacman.conf`` with the pacman configuration.


## 2. Booting the Live environment

After booting into the live environment, we'll have to check a couple of things before we start:

* Use ``loadkeys`` in order to load a different keymap if you don't use QWERTY (e.g. ``loadkeys de`` or ``loadkeys colemak``)
* Use ``ping 1.1.1.1`` to check if we have internet connectivity. Networking should work out of the box for wired networks. Wireless networking can be setup using ``iwctl`` [^3]
* Ensure, that the ``zfs`` and ``zpool`` commands exist

[^3]: <https://wiki.archlinux.org/title/Iwd#Connect_to_a_network>

## 3. Partitioning your drive
At this point, we can begin preparing our drive. First, identify the device you want to use as your bootdrive by using ``lsblk``.

``` bash
$ lsblk
    NAME  MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
    loop0   7:0    0 784.6M  1 loop /run/archiso/airootfs
    sr0    11:0    1 943.3M  0 rom  /run/archiso/bootmnt
    sda   254:0    0    50G  0 disk 
```

In this example, I am going to use ``/dev/sda`` as my boot drive. If your system boots off of an NVMe drive, then this will likely
be ``/dev/nvme0n1`` or similar for you. We are going to use ``cgdisk`` in order to partition the drive (WARNING: This will delete
the data that is currently on that drive):


* Create one partition of 500M in size. This will be the EFI partition that your System uses for booting. Use the type ``EF00`` to indicate the pact that this is a EFI partition
* Create a secondary partition that spans the rest of your drive. The type for this drive is not really important.  Popular choices are ``bf00`` (Solaris root, Solaris being the 'original' ZFS supporting OS), ``8300`` (Linux Filesystem) or ``8304`` (Linux root) [^zfs-partition-type]


[^zfs-partition-type]: <https://zfsonlinux.topicbox.com/groups/zfs-discuss/T5177f234d7c777ab-M68f3f3eee18142560b193538/proper-partition-type-linux>

```
                            cgdisk 1.0.9 

                        Disk Drive: /dev/vda
                      Size: 104857600, 50.0 GiB

 Part. #     Size        Partition Type            Partition Name
 ----------------------------------------------------------------
             1007.0 KiB  free space
    1        500.0 MiB   EFI system partition      EFI
    2        49.5 GiB    Linux filesystem          zroot
             1007.5 KiB  free space
```
_Partition setup inside cgdisk [^partitioning-note]_

[^partitioning-note]: Depending on the size and layout of your disk, free space may be inserted automatically. This is normal.

Write the partition and exit ``cgdisk`` . Now, ``lsblk`` should indicate 2 drives: ``/dev/sda1`` (will be EFI) and ``/dev/sda2`` (will be ZFS). 

```bash
root@archiso ~ $ lsblk         
    NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
    loop0    7:0    0 784.6M  1 loop /run/archiso/airootfs
    sr0     11:0    1 943.3M  0 rom  /run/archiso/bootmnt
    sda    254:0    0    50G  0 disk 
    ├─sda1 254:1    0   500M  0 part 
    └─sda2 254:2    0  49.5G  0 part 
```

As a final step during the partitioning phase, we will use ``mkfs.vfat /etc/sda1`` in order to initialize a FAT filesystem for the EFI drive.
Unfortunately, FAT is all EFI supports [^efi-wikipedia] - this limitation applies to all filesystems however. Even for default ext4 Linux systems, a FAT system
is always involved in booting. This fact doesn't matter however, because the EFI partition only contains reltaively volatile data: It can be
recreated from the data in the system at any point in time (In case of data loss & reinstallation, be sure to recreate the EFI data using the ``grub-install`` command). 

[^efi-wikipedia]: <https://en.wikipedia.org/wiki/EFI_system_partition>

## 4. Setup ZFS

After all of this preparation, it is finally time to start with the interesting part: Actually setting up the ZFS zpool and datasets. In this part I will assume
that you are at least faintly familiar with the concepts behind ZFS, but will do my best to describe them as we go. To skip ahead a bit, the following is
the dataset setup we are setting up:

```bash
root@archiso ~ $ zfs list
    NAME                USED  AVAIL     REFER  MOUNTPOINT
    zroot              1.01M  48.0G       96K  none
    zroot/DATA          288K  48.0G       96K  none
    zroot/DATA/docker    96K  48.0G       96K  /var/lib/docker
    zroot/DATA/home      96K  48.0G       96K  /home
    zroot/ROOT           96K  48.0G       96K  /
```

Under our root dataset, there are 2 'logical' datasets which are not mounted but used to logically separate the data:

* ``ROOT`` will contain our system root
* ``DATA`` will contain datasets for userdata (e.g. home directory, docker files, ...)


This distinction is done because snapshots are created on a per-dataset basis. By separating system data and user data well, we will be able to roll back
a failed system upgrade in the future without compromising our user data. For alternative dataset configurations, check "4.2 Aside: Alternative dataset
configurations"

We are going to start out creating a zpool, which describes an array of one or more physical disks that are handled as a single unit by ZFS. When using
multiple disks, ZFS can arrange them in multiple RAID configurations, but for this simple guide we are going to assume that a single drive is being used.
A zpool then contains datasets: You can think of these as a cross between directories and partitions. Creating a zpool will always also create a dataset
with the same name.

```bash
$ zpool create \
    zroot \
    -o ashift=12 \
    -O acltype=posixacl \
    -O relatime=on \
    -O xattr=sa \
    -O mountpoint=none \
    -O canmount=off \
    -R /mnt \
    /dev/sda2
```

Now that's a lot of options. Options set with ``-o`` are options we are setting on the zpool while options set with ``-O`` are those we are setting on the dataset.
Let's go through these options 1 by 1 and explain what each one does. I will also include information if the option is strictly necessary (as in, this option is
required for booting) or recommended (as in, you should probably set the option for optimal performance and compatibility)


* ``zroot`` is the name of the zpool we are creating. You are free to call it whatever you want, but it seems that for this use case ``zroot`` has been agreed upon as a convention.
* ``-o ashift=12`` sets the pools blocksizes to 4K (2 ^ 12 bytes). This should match the sectorsize of modern hard drives and can be only set once when creating the pool. This value is sometimes incorrectly detected as 9, making the pools performance less than suboptimal. [^zfs-ashift]
* ``-O acltype=posixacl`` instructs ZFS to use POSIX compatible ACLs (Access Control Lists). This option is not strictly required on the root partition for the whole system - but at least ``/var/log/journal`` needs it to be set. [^zfs-posixacl-journald]
* ``-O xattr=sa`` Sets the storage mechanism for extended attributes. Some applications and use cases (including the ACLs mentioned above) add additional metadata to files and ZFS has multiple mechanisms of storing it. ``xattr=sa`` will instruct ZFS to save the metadata on the files inode itself. This means that reading metadata does not cause another read operation, making reading and writing metadata more performant [^zfs-xattr-performance]. This comes at the cost of compatibility, however: At the time of writing, only ZFS on Linux and supposedly OpenZFS on macOS support this xattr storage (Although I haven't tested the latter. Check the openzfs wiki [^zfs-xattrs-support] for more information). Your ZFS dataset will still be mountable and accessible on FreeBSD, but extended attributes will be lost. Since this dataset is meant to be used exclusively with Linux, the lack of compatibility is ok.
* ``-O relatime=on`` by default, most filesystems will save file access timestamps for files. This however means, that file metadata has to be refreshed every single time a file is accessed, making a read equal to a read and a write. This behaviour can be disabled by ``atime=off`` in order to disable tracking of accesstime completely. ``relatime`` is a compromise between atime tracking and no atime tracking, saving the timestamp only when the file is updated or if the last access is more than 24h in the past, making it not write for every read operation[^atime-vs-relatime]. It is said, that for maximum compatibility, ``relatime`` should be used on systems - I for my part have run ext4 based systems ``noatime`` (equivalent of ZFS ``atime=off``) for more than 10 years at this point and haven't had an issue so far. So if you want to be safe, use ``relatime=on`` - if you want things to be more efficient, use ``atime=off`` 
* ``-O mountpoint=none`` & ``-O canmount=off`` Tells ZFS that this dataset is not mountable. It will only exist as a way to structure the rest of our datasets.
* ``-R /mnt`` Is not an option for the zpool itself, but instructs ZFS to mount our datasets relative to ``/mnt``. This means our root dataset with ``mountpoint=/`` will be mounted at ``/mnt`` instead.
* ``/dev/sda2`` Is the identifier of the blockdevice we want to use for our zpool


[^zfs-ashift]: <https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Workload%20Tuning.html#alignment-shift-ashift>
[^zfs-posixacl-journald]: <https://askubuntu.com/questions/970886/journalctl-says-failed-to-search-journal-acl-operation-not-supported>
[^zfs-xattr-performance]: <https://github.com/openzfs/zfs/commit/82a37189aac955c81a59a5ecc3400475adb56355>
[^zfs-xattrs-support]: <https://openzfs.org/wiki/Features#SA_based_xattrs>
[^atime-vs-relatime]: <https://blog.confirm.ch/mount-options-atime-vs-relatime/>

At this point we can start adding datasets to our pool:

```bash
root@archiso ~ $ zfs create -o mountpoint=/ -o canmount=noauto zroot/ROOT
root@archiso ~ $ zfs create zroot/DATA
root@archiso ~ $ zfs create -o mountpoint=/home zroot/home
root@archiso ~ $ zfs create -o mountpoint=/var/lib/docker zroot/DATA/docker
```

A couple of additional notes about the datasets:

* Use ``-o mountpoint=...`` to set the mountpoint of the dataset if the parent does not have a mountpoint
* The dataset for the system-root (``zroot/ROOT``) has to have ``canmount=noauto``
    * By default, when importing a zpool, ZFS will automatically mount all datasets on it. ``canmount=noauto`` tells ZFS that while this dataset is mountable, it should not be mounted automatically. Then booting, initramfs automatically mounts the root dataset, so ZFS will not have to mount it later on [^zfs-automount]. In live environments this means that we'll have to mount it manually using ``zfs mount {DATASET_NAME}``. Setting ``canmount=noauto`` is required for the root dataset.

[^zfs-automount]: https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.hook#L110


To double check, that a) the pool is setup correctly and b) all datasets are mounted correctly we are going to export and reimport the pool (ZFS calls the process of removing a pool from the
system "exporting" and adding it "importing". Think of it as ejecting and inserting a thumb drive).

```bash
root@archiso ~ $ zpool export zroot
root@archiso ~ $ zpool import -R /mnt -N zroot
root@archiso ~ $ zfs mount zroot/ROOT
root@archiso ~ $ zfs mount -a 
```

A couple of notes about this:

* We are using ``-R /mnt`` and ``-N`` for ``zpool import``. We have used ``-R /mnt`` before when creating the pool: It causes the datasets to be mounted relative to ``/mnt``. ``-N`` causes no datasets to be mounted by default. This is important because our root dataset has ``canmount=noauto`` set and mounting other datasets automatically would cause them to be mounted in the wrong order
* ``zfs mount -a`` mounts all datasets that are automatically mountable


### 4.1 Aside: Device identifier


* Usually the recommendation with ZFS is to use /dev/disk/by-id/... instead of /dev/... device IDs since they are more stable
* With this kind of use case however, I opted for the more unstable /dev/... identifier in order to make switching physical hard drives easier
* If you use /dev/disk/by-id/... you have to set the environment variable ``ZPOOL_VDEV_NAME_PATH`` in order for grub to be installable correctly [^zfs-grub-vdev-name-path] 


### 4.2 Aside: Alternative dataset configurations

There are multiple alternative ways of structuring your datasets that mostly come down to personal preference. The main rules (``/`` having to have ``canmount=noauto``)
are the same for all of them - they just differ in the dataset layout.

#### 4.2.1 Multiple roots
Some guides place the system root in ``zroot/ROOT/default`` in order to add support for multiple systems booting from the same pool with the same data directories.

```bash
root@archiso ~ $ zfs list 
    NAME                 USED  AVAIL     REFER  MOUNTPOINT
    zroot               1.11M  48.0G       96K  none
    zroot/DATA           288K  48.0G       96K  none
    zroot/DATA/docker     96K  48.0G       96K  /var/lib/docker
    zroot/DATA/home       96K  48.0G       96K  /home
    zroot/ROOT           192K  48.0G       96K  none
    zroot/ROOT/default    96K  48.0G       96K  /
```

#### 4.2.2 Not separating ROOT & DATA
The simplest setup would be to use the root-dataset as the system root mounted at ``/`` and children datasets for data. Since by default datasets define a directory tree as
well, you will have to be careful to set ``canmount=off`` on parent-datasets of your data-directories in order to have all system data in the root dataset instead of scattered
across multiple ones.

```bash
root@archiso ~ $ zfs list                          
    NAME                   USED  AVAIL     REFER  MOUNTPOINT
    zroot                 1.18M  48.0G       96K  /
    zroot/home              96K  48.0G       96K  /home
    zroot/var              288K  48.0G       96K  /var
    zroot/var/lib          192K  48.0G       96K  /var/lib
    zroot/var/lib/docker    96K  48.0G       96K  /var/lib/docker
root@archiso ~ $ zfs get canmount                  
    NAME                  PROPERTY  VALUE     SOURCE
    zroot                 canmount  noauto    local
    zroot/home            canmount  on        local
    zroot/var             canmount  off       local
    zroot/var/lib         canmount  off       local
    zroot/var/lib/docker  canmount  on        local
```

## 5. Bootstrap System

At this, we can bootstrap an arch system using ``pacstrap`` as we would with regular systems.
For more detailed information, check the arch installation guide [^arch-installation-guide]

[^arch-installation-guide]: <https://wiki.archlinux.org/title/Installation_guide#Installation>

Before we start, we will have to mount our EFI partition as ``/mnt/boot/efi``

```bash
root@archiso ~ $ mkdir -p /mnt/boot/efi
root@archiso ~ $ mount /dev/sda1 /mnt/boot/efi
root@archiso ~ $ pacstrap /mnt base base-devel linux linux-firmware linux-headers dkms efibootmgr grub neovim
```

Note the inclusion of ``linux-headers`` and ``dkms`` - these packages will be required a bit later on when installing zfs inside the bootstrapped system. I also installed
``neovim`` here as it is my preferred text editor. If you prefer a different text editor, then install a different one - you'll just need *something* to edit files in a second.

In preparation for a later step, we can also generate a ``/etc/fstab`` file for our new system and immediately edit it: The script will include all mount points, however
most of them are handled by zfs and don't need to be in ``/etc/fstab``. Only the non-zfs (in this case only the EFI-partition) need to be in the file. Comment out all
ZFS datasets in the resulting ``/mnt/etc/fstab`` and save the file.

```bash
root@archiso ~ $ genfstab -U /mnt >> /mnt/etc/fstab
```


## 6. Install ZFS

At this point we have a bootstrapped archlinux system in ``/mnt`` but that system a) does not know about zfs and b) is not bootable. First, we are going to tackle the
first point: Install zfs inside the new system.

ZFS can save data about a zpool in a cachefile. In a later step, the ``zfs`` hook will copy the cachefile into the initramfs in order for our booting kernel to know where
to find the pool. This however is where a bit of weirdness comes in: The zpool cache is generated by the ZFS kernel module in the main system hosting the kernel.
This means we will have to first create the cache and then copy it into the new system by hand.

```bash
root@archiso ~ $ mkdir -p /mnt/etc/zfs
root@archiso ~ $ zpool set cachefile=/etc/zfs/zpool.cache zroot 
root@archiso ~ $ cp /etc/zfs/zpool.cache /mnt/etc/zfs/zpool.cache
```

Note: You are not free to choose any path. ``/etc/zfs/zpool.cache`` is hardcoded in the initramfs hook for zfs. 
This surprised me, but you can check for yourself in  ``/usr/lib/initcpio/install/zfs`` [^zpool-path]

[^zpool-path]: <https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.install#L44>

At this point, it is time to use ``arch-chroot`` in order to change into our newly installed system in order to continue the installation process. We will continue by adding
the archzfs pacman repository and keys as we did earlier while creating the ISO.

```bash
root@archiso ~ $ arch-chroot /mnt                  
[root@archiso /]$ echo -e "\n[archzfs]\nServer = https://archzfs.com/\$repo/\$arch\n" >> /etc/pacman.conf 
[root@archiso /]$ pacman-key -r DDF7DB817396A49B2A2723F7403BD972F75D9D76
[root@archiso /]$ pacman-key --lsign-key DDF7DB817396A49B2A2723F7403BD972F75D9D76
```

Especially adding the keys can be a bit awkward on real hardware as it includes transcribing a hash from another screen.
In case you are using the archiso I built (see Step 1), you can make this step easier by using scripts I have built in:

```bash
root@archiso ~ $ cat /zfs-pacman.conf >> /mnt/etc/pacman.conf
root@archiso ~ $ cp /zfs-key.sh /mnt/zfs-key.sh
root@archiso ~ $ arch-chroot /mnt              
[root@archiso /]$ /zfs-key.sh
```


Now we can install ZFS by installing the ``zfs-dkms`` and ``zfs-utils`` packages:

```bash
[root@archiso /]$ pacman -Sy zfs-dkms zfs-utils
```

Now, ZFS should be installed. We can confirm this by using the ``zfs`` and ``zpool`` commands.

```bash
[root@archiso /]$ zpool list 
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zroot  49.5G  2.48G  47.0G        -         -     0%     5%  1.00x    ONLINE  /mnt
[root@archiso /]$ zfs list 
NAME                USED  AVAIL     REFER  MOUNTPOINT
zroot              2.45G  45.5G       96K  none
zroot/DATA          288K  45.5G       96K  none
zroot/DATA/docker    96K  45.5G       96K  /mnt/var/lib/docker
zroot/DATA/home      96K  45.5G       96K  /mnt/home
zroot/ROOT         2.45G  45.5G     2.45G  /mnt
```

At this point we can activate the following systemd services to ensure that ZFS will be initialized correctly upon boot:

* ``systemctl enable zfs.target``
* ``systemctl enable zfs-import-cache.service``
* ``systemctl enable zfs-mount.service``
* ``systemctl enable zfs-import.target``


### 6.1 Aside: zfs-linux vs zfs-dkms

The archzfs repository contains 2 different ways of installing ZFS [^archzfs-variants]: 

* The ``zfs-linux`` (or ``archzfs-linux-lts``, ``archzfs-linux-zen``, ...) packages provides the kernel modules specific to these kernels
* The ``zfs-dkms`` uses dkms [^dkms] in order to be compatible with all kernel versions. This comes at a cost of having to rebuild the kernel module every time you switch or upgrade the kernel.


I am opting to use the latter for 2 reasons:

1. It reduces the mental load of having to install the correct package for your kernel
2. I have been running into version incompatibilities between the kernel package and the zfs package due to the archlinux kernel being more recent than the archzfs repository anticipated


[^archzfs-variants]: <https://github.com/archzfs/archzfs/wiki#included-package-groups>
[^dkms]: <https://wiki.archlinux.org/title/Dynamic_Kernel_Module_Support>

## 7. Configure bootloader & kernel images

With our system bootstrapped and ZFS installed in it, it is time to get it into a bootable state. Mainly this means configuring initramfs to mount ZFS datasets and installing grub
as a bootloader.

The high-level overview of how a linux system usually boots is as follows:

* The mainboards EFI is configured to start a bootloader (in our case grub)
* The bootloader then loads an image (the so called initramfs), which contains the kernel and the minimum set of applications in order to start the rest of the system. The bootloader also passes certain configuration to that initramfs image.
* initramfs is responsible for is tasked with bringing the system into a running state. This mainly includes mounting the system root partition (or dataset) as ``/``. If the system partition is encrypted, then initramfs is also responsible for decrypting the partition (e.g. by prompting the user for a password)


To add ZFS support to this whole chain of events, first we'll have to add the ``zfs`` hook to ``/etc/mkinitcpio.conf``. The hook should be added before ``filesystems`` and ``keyboard`` should be added before ``zfs``. ``fsck`` is specific to journaling filesystems, so it is not important for zfs. As such, the resulting line should look as follows:

    HOOKS=(base udev autodetect modconf block keyboard zfs filesystems)

Save the file and use ``mkinitcpio`` to regenerate the initramfs images:

```bash
[root@archiso /]$ mkinitcpio -P
```

Now we'll have to configure grub to pass down the correct bootdevice configuration to the initramfs image. To do this, edit ``/etc/default/grub`` and adjust the ``GRUB_CMDLINE_LINUX=`` variable.
Add ``root=zfs`` and ``zfs={ROOT_DATASET_NAME}``

    GRUB_CMDLINE_LINUX="root=zfs zfs=zroot/ROOT"

Lastly, we'll need to install grub as a EFI boot option and generate its config:

```bash
[root@archiso /]$ grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=arch-zfs
[root@archiso /]$ grub-mkconfig -o /boot/grub/grub.cfg
```

Notes about this:

* ``--bootloader-id`` can be any string. It is what will show up in your EFI configuration
* If you have setup your zpool using a disk id in place of the disk path (e.g. ``/dev/disk/by-id/...`` instead of ``/dev/...``), then ``grub-mkconfig`` will likely fail with ``grub-install: error: failed to get canonical path of `/dev/bus-Your_Disk_ID-part#``'. In this case you will have to set the environment variable ``ZPOOL_VDEV_NAME_PATH=1`` [^zfs-grub-vdev-name-path]. To set it globally for future grub config updates, add it to ''/etc/profile'


[^zfs-grub-vdev-name-path]: <https://openzfs.github.io/openzfs-docs/Getting%20Started/Arch%20Linux/Root%20on%20ZFS/5-bootloader.html>


### 7.1 Aside: bootfs

An alternative approach to setting the dataset that should be used for booting is setting the `bootfs` parameter on the pool.
This way the dataset name can be changed much more easily without having to go through grub config.

To do so, use `root=zfs zfs=bootfs` in `/etc/default/grub` and set the `bootfs` option on the zpool:

```bash
$ zpool set bootfs=zroot/ROOT zroot 
```

This method can be interesting in order to more easily boot off of a snapshot of your system. I personally prefer the simplicity of
setting the dataset name directly in the grub config. If a system upgrade goes wrong, I will more likely completely rollback the
dataset to the last snapshot instead of booting off of the snapshot itself.

### 7.2 Aside: Grub root= format

There are 2 formats to specify the ``root=...`` string in ``/etc/default/grub``:

* ``root=zfs zfs={DATASET_NAME}``
* ``root=ZFS={DATASET_NAME}`` 


Both do the same thing - when researching the topic, you will see some guides use one format and others use the other.
If you are curious about more details as well as additional options, check out the mkinitcpio install script [^zfs-intiramfs-install]
as well as the script that will be embedded in the initramfs [^zfs-initramfs-script]. There's much less magic in there than you might think.

[^zfs-intiramfs-install]: <https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.install>
[^zfs-initramfs-script]: <https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.hook>

## 8. Configure Rest of the System

At this point, all ZFS specific configuration has been done, and we'll have to finish configuring the system. This is not ZFS specific, so I will glaze over this. If you want more
information about this step, check out the arch installation guide [^arch-install-guide]

[^arch-install-guide]: <https://wiki.archlinux.org/title/Installation_guide#Configure_the_system>

```bash
[root@archiso /]$ ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime
[root@archiso /]$ hwclock --systohc
[root@archiso /]$ nvim /etc/locale.gen
[root@archiso /]$ locale-gen
Generating locales...
    de_DE.UTF-8... done
    en_DK.UTF-8... done
    en_US.UTF-8... done
Generation complete.
[root@archiso /]$ echo -e "LANG=en_US.UTF-8\nLV_TIME=en_DK.UTF-8" > /etc/locale.conf
[root@archiso /]$ echo 'KEYMAP=colemak' > /etc/vconsole.conf    $ Or your preferred keyboard layout
[root@archiso /]$ echo 'arch-zfs-testmachine' > /etc/hostname
[root@archiso /]$ passwd
```

If you need to connect to Wi-Fi or have your IP address configured via DHCP, you should also install ``iwd`` and ``dhcpcd`` 


## 9. Reboot

Use ``exit`` to exit the chroot environment and ``reboot`` to reboot your system. You should now boot into your newly installed archlinux system running on ZFS.


![A freshly booted ArchLinux installation running on top of ZFS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pgospjmee4tnp37ut2ax.png)
*A freshly booted ArchLinux installation running on top of ZFS*

## 10. Honorable mentions


There are a couple of guides on installing ZFS on archlinux:

* The official OpenZFS documentation contains a section named "Root on ZFS" [^openzfs-root]. This is the most complete guide, but it guides you through an extremely complicated setup. I don't recommend using this guide directly - but it is very helpful as a reference
* Arch-Wiki contains a page on installing arch on ZFS [^arch-root]. It is not as complicated as the official guide, but does not explain a lot of things
* The YouTube channel "Stephens Tech Talks" has a video guide [^video-root] which is the simplest guide so far, showing a full runthrough of the whole thing. Mostly mirrors the arch guide, but guides you through a 'golden path'. Really, this was the first guide I had found that made me understand what was going on.

[^openzfs-root]: https://openzfs.github.io/openzfs-docs/Getting%20Started/Arch%20Linux/Root%20on%20ZFS/0-overview.html
[^arch-root]: https://wiki.archlinux.org/title/Install_Arch_Linux_on_ZFS
[^video-root]: https://www.youtube.com/watch?v=CcSjnqreUcQ