Skip to main content

Three Single-Boot Arch Variants: Btrfs, LUKS, or Both

arch linux
linux
installation
btrfs
luks
snapper
grub
mkinitcpio
zram
single-boot
A side-by-side compilation of three single-boot Arch Linux install procedures derived from what I learned writing Articles 01–04: Arch + Btrfs subvolumes (no encryption), Arch + LUKS (no Btrfs), and Arch + LUKS + Btrfs (the snapshot-ready encrypted single-boot from Article 03). Use the tab switcher to pick the variant you want. Each tab is a complete, copy-pasteable procedure isolated to one variable change relative to the basic install, and stops on the console — the desktop layer is a separate choice.
Author

Evanns Morales-Cuadrado

Published

May 18, 2026

Honesty disclaimer: I haven’t run these three exactly line-by-line

Unlike Article 02 (basic install), Article 01 (failed dual-boot post-mortem), and Article 03 (LUKS+Btrfs single-boot) — which are first-hand install logs I executed and can vouch for command-by-command — the three procedures in this compilation are reasoned derivations. They’re built from what I learned in those four articles plus the Arch wiki and standard practice for each technology in isolation. I’ve verified every command in the man pages and the wiki, but I have not run all three end-to-end on a real machine and watched them boot.

If you spot something wrong, the canonical sources are:

Treat this as a side-by-side reference for the deltas between variants, not as a substitute for reading the wiki on the technology you’re about to put under your data.

How to use this article

Pick the tab that matches the install you want. Each tab is fully self-contained — it tells you the partition layout, the formatting commands, the mkinitcpio hooks, the GRUB cmdline, and how to verify the boot. Where a tab is identical to Article 02 (the basic install), I point at the relevant section instead of restating it; the tabs are written as diffs against the basic install, not full re-derivations.

Common prerequisites for all three tabs (same as the basic install):

  • A booted Arch live ISO (archlinux-2026.05.01-x86_64.iso or newer)
  • Working network (iwctl for Wi-Fi or wired DHCP)
  • NTP synced (timedatectl set-ntp true)
  • Disk identified — these examples use /dev/nvme0n1, substitute your device

What this variant gives you, vs. Article 02: the same plain (unencrypted) install, but the root filesystem is Btrfs with proper subvolumes instead of ext4. That means bootable snapshots via snapper + grub-btrfs, copy-on-write compression (compress=zstd:3), and the ability to roll back the entire system to a known-good state after a bad upgrade.

What’s the same as Article 02: UEFI boot, GRUB, systemd, the user/locale/hostname setup, the package installation flow.

What’s different: the formatting step, the subvolume layout, the mount options, the BINARIES line in mkinitcpio.conf, the GRUB cmdline.

Partition layout

Same three partitions as the basic install — but the root partition will hold Btrfs subvolumes instead of one ext4 filesystem:

Partition Size Filesystem Mount Purpose
p1 1 GiB FAT32 /boot UEFI EFI System Partition
p2 8 GiB swap [SWAP] Swap (no zram in this variant; add it later if you want)
p3 rest Btrfs /, /home, /.snapshots, /var/log Root with subvolumes

Partition with cfdisk exactly as in Article 02 Chapter 1, then:

mkfs.fat -F32 /dev/nvme0n1p1
mkswap /dev/nvme0n1p2
mkfs.btrfs -L arch /dev/nvme0n1p3

Create subvolumes

mount /dev/nvme0n1p3 /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@snapshots
btrfs subvolume create /mnt/@var_log
umount /mnt

The four-subvolume layout is the snapper-friendly standard: @ is root, @home is /home, @snapshots holds the snapshots themselves (must be a subvolume, not a directory, so snapper can roll back without recursive trouble), and @var_log keeps logs out of any root snapshots you take.

Mount subvolumes with the right options

mount -o subvol=@,compress=zstd:3,noatime /dev/nvme0n1p3 /mnt
mkdir -p /mnt/{boot,home,.snapshots,var/log}
mount -o subvol=@home,compress=zstd:3,noatime      /dev/nvme0n1p3 /mnt/home
mount -o subvol=@snapshots,compress=zstd:3,noatime /dev/nvme0n1p3 /mnt/.snapshots
mount -o subvol=@var_log,compress=zstd:3,noatime   /dev/nvme0n1p3 /mnt/var/log
mount /dev/nvme0n1p1 /mnt/boot
swapon /dev/nvme0n1p2

compress=zstd:3 is the level the Arch wiki recommends — good ratio, low CPU. noatime prevents needless metadata churn on every read.

Pacstrap + fstab

pacstrap -K /mnt base base-devel linux linux-firmware linux-headers \
  btrfs-progs grub efibootmgr networkmanager \
  vim nano sudo git man-db man-pages texinfo
genfstab -U /mnt >> /mnt/etc/fstab

The new package here vs. Article 02 is btrfs-progs. genfstab -U will detect subvolume mount options correctly and emit four matching /dev/disk/by-uuid/<UUID> entries (one per subvolume) plus the EFI and swap entries. Sanity-check it:

cat /mnt/etc/fstab | grep -E 'subvol=|UUID='

You should see four subvol=@… lines with compress=zstd:3,noatime options.

Inside the chroot — mkinitcpio and GRUB

arch-chroot /mnt

Locale, time, hostname, user, sudo — same as Article 02 Chapter 6.

/etc/mkinitcpio.conf needs btrfs added to BINARIES:

BINARIES=(btrfs)
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block filesystems fsck)

The default HOOKS= line already covers a Btrfs root — no encryption hook needed because there’s no LUKS layer. Rebuild:

mkinitcpio -P

GRUB on a Btrfs root needs no kernel-cmdline changes vs. a plain ext4 install — Btrfs is detected and mounted automatically from the root device’s UUID. Install GRUB exactly as Article 02 Chapter 11:

grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
grub-mkconfig -o /boot/grub/grub.cfg

Snapper + grub-btrfs (the payoff)

Still inside the chroot:

pacman -S snapper grub-btrfs snap-pac
snapper -c root create-config /
snapper -c home create-config /home    # optional — see Article 03 on whether to snapshot /home
systemctl enable snapper-timeline.timer snapper-cleanup.timer
systemctl enable grub-btrfsd.service

snap-pac is the pacman hook that takes pre+post snapshots around every pacman transaction — so every pacman -Syu is reversible. grub-btrfsd watches @snapshots and rebuilds the GRUB menu so you can boot into a snapshot.

Verify, exit, reboot

exit                          # leave chroot
umount -R /mnt
swapoff -a
reboot

After first boot:

findmnt -t btrfs -o TARGET,SOURCE,OPTIONS
# expect 4 rows: /, /home, /.snapshots, /var/log — all with subvol=@…,compress=zstd:3
sudo snapper -c root list
# expect at least the post-install snapshot

If both look right, the variant is working as designed.

What this variant gives you, vs. Article 02: the same plain (unencrypted-filesystem) install, but the root partition is wrapped in a LUKS2 container so the data on disk is encrypted at rest. A boot-time passphrase prompt is the only addition to the user-visible flow.

What’s the same as Article 02: UEFI boot, GRUB, systemd, the ext4 root filesystem, no subvolumes, no zram, the user/locale/hostname setup.

What’s different: an extra cryptsetup luksFormat step before mkfs, a cryptsetup open to expose the mapper device, the BINARIES/HOOKS lines in mkinitcpio.conf, the GRUB cmdline with cryptdevice= / rd.luks.name=, an /etc/crypttab entry.

Partition layout

Same three partitions as the basic install — but the root partition is a LUKS container, not a raw filesystem:

Partition Size Filesystem Mount Purpose
p1 1 GiB FAT32 /boot UEFI ESP (must stay unencrypted — GRUB has to read it)
p2 8 GiB swap (inside its own LUKS, optional) [SWAP] Encrypted swap, see note below
p3 rest LUKS2 → ext4 / Encrypted root

Two notes on the swap partition:

  • For this variant I’m assuming you want an unencrypted regular partition swap-only (no hibernation). If you want hibernation-safe encrypted swap, see Article 01’s plan — but be aware Article 01 didn’t boot, so use the Article 03 swap-file-in-LUKS pattern instead (which lives in the LUKS + Btrfs tab on this page).
  • The most-common simple pattern in 2026 is skip the swap partition entirely and use zram. Add zram-generator after first boot.

Format and open the LUKS container

Partition first with cfdisk as in Article 02 Chapter 1, then create the LUKS container on the root partition:

cryptsetup luksFormat --type luks2 /dev/nvme0n1p3
# Type YES, then enter a strong passphrase twice.
cryptsetup open /dev/nvme0n1p3 cryptroot

Now /dev/mapper/cryptroot is the unlocked block device. Format ext4 inside it:

mkfs.fat -F32 /dev/nvme0n1p1
mkswap /dev/nvme0n1p2
mkfs.ext4 -L arch /dev/mapper/cryptroot
The mapper name cryptroot matters

You’ll reference this name three times: here in cryptsetup open, in the GRUB cmdline (rd.luks.name=…=cryptroot), and as /dev/mapper/cryptroot in the kernel root= parameter. They have to match exactly. A typo here is the most common reason the install boots into emergency mode — exactly the failure described in Article 01.

Mount, pacstrap, fstab

mount /dev/mapper/cryptroot /mnt
mkdir -p /mnt/boot
mount /dev/nvme0n1p1 /mnt/boot
swapon /dev/nvme0n1p2

pacstrap -K /mnt base base-devel linux linux-firmware linux-headers \
  cryptsetup grub efibootmgr networkmanager \
  vim nano sudo git man-db man-pages texinfo

genfstab -U /mnt >> /mnt/etc/fstab

The new package vs. Article 02 is cryptsetup (so the running system can re-open the LUKS container after boot, even though the initramfs already opened it). genfstab -U writes /dev/mapper/cryptroot as the root device using its UUID — that’s fine, the initramfs hook below replaces it functionally.

Inside the chroot — mkinitcpio, crypttab, GRUB

arch-chroot /mnt

Locale, time, hostname, user, sudo — same as Article 02 Chapter 6.

The critical edit is /etc/mkinitcpio.conf. Pick one of two valid hook layouts (I recommend sd-encrypt — it’s the modern systemd-based path Article 03 uses):

# Modern (systemd-based) — pairs with kernel cmdline rd.luks.name=…
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck)

Or the legacy busybox path:

# Legacy (busybox-based) — pairs with kernel cmdline cryptdevice=…
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt filesystems fsck)

The two are equivalent in outcome but use different kernel cmdline syntax (see GRUB below). Once chosen, rebuild:

mkinitcpio -P

For sd-encrypt, the GRUB cmdline goes:

# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel=3 rd.luks.name=<LUKS-UUID>=cryptroot root=/dev/mapper/cryptroot rw"

Where <LUKS-UUID> is blkid /dev/nvme0n1p3 -s UUID -o value (the partition UUID, not the mapper UUID).

For the legacy encrypt hook:

GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel=3 cryptdevice=UUID=<LUKS-UUID>:cryptroot root=/dev/mapper/cryptroot rw"
GRUB_ENABLE_CRYPTODISK=y — when you need it

This setting tells GRUB itself to handle the LUKS prompt before the kernel even loads. Required only if you put /boot inside the LUKS container (rare). In this variant /boot is its own unencrypted FAT32 partition, so the kernel loads in cleartext, then prompts for LUKS to unlock the rootfs. No GRUB_ENABLE_CRYPTODISK=y is needed here. Leave it commented out.

Install GRUB:

grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
grub-mkconfig -o /boot/grub/grub.cfg

Verify, exit, reboot

exit
umount -R /mnt
swapoff -a
cryptsetup close cryptroot
reboot

Expected boot sequence: GRUB menu → kernel loads → “Please enter passphrase for disk … (cryptroot):” → ext4 root mounts → SDDM/TTY login.

cryptsetup status cryptroot
# expect: active LUKS2 mapping pointing at /dev/nvme0n1p3
lsblk -f
# expect: nvme0n1p3 = crypto_LUKS, mapper cryptroot = ext4 mounted at /

What this variant gives you: the full single-boot install from Article 03, condensed. LUKS2 underneath, Btrfs subvolumes on top, zram for everyday swap, a NoCOW Btrfs swap file for hibernation, snapper + grub-btrfs from day one. Stops on the console — desktop layer is your call (Caelestia, custom Hyprland, GNOME, Plasma, sway, dwm, …).

What’s the same as Article 03: every command in Parts 1–3 of that article — partitioning, LUKS format, Btrfs subvolume layout, mount options, pacstrap, mkinitcpio hooks, GRUB cmdline, snapper config, first-boot verification.

What’s different: this tab is a quick procedural distillation — read it as a checklist. The full prose explanations (why each cryptdevice= option is shaped the way it is, why compress=zstd:3 specifically, why a NoCOW swap file vs. an encrypted swap partition) live in Article 03.

Partition layout (same as Article 03)

Partition Size Filesystem Mount Purpose
p1 1 GiB FAT32 /boot UEFI ESP
p2 rest LUKS2 → Btrfs /, /home, /.snapshots, /var/log, /swap Encrypted root with 5 subvolumes

No separate swap partition — zram + a NoCOW swap file inside @swap cover both everyday compression and hibernation.

Format, open, layout

# After cfdisk creates p1 (1 GiB EFI) and p2 (rest)
mkfs.fat -F32 /dev/nvme0n1p1
cryptsetup luksFormat --type luks2 /dev/nvme0n1p2
cryptsetup open /dev/nvme0n1p2 cryptroot
mkfs.btrfs -L arch /dev/mapper/cryptroot

mount /dev/mapper/cryptroot /mnt
for sv in @ @home @snapshots @var_log @swap; do
  btrfs subvolume create "/mnt/$sv"
done
umount /mnt

Mount with the right options

mount -o subvol=@,compress=zstd:3,noatime /dev/mapper/cryptroot /mnt
mkdir -p /mnt/{boot,home,.snapshots,var/log,swap}
mount -o subvol=@home,compress=zstd:3,noatime      /dev/mapper/cryptroot /mnt/home
mount -o subvol=@snapshots,compress=zstd:3,noatime /dev/mapper/cryptroot /mnt/.snapshots
mount -o subvol=@var_log,compress=zstd:3,noatime   /dev/mapper/cryptroot /mnt/var/log
mount -o subvol=@swap,noatime                       /dev/mapper/cryptroot /mnt/swap
mount /dev/nvme0n1p1 /mnt/boot

Pacstrap + fstab

pacstrap -K /mnt base base-devel linux linux-firmware linux-headers \
  btrfs-progs cryptsetup grub efibootmgr networkmanager \
  snapper snap-pac grub-btrfs zram-generator \
  vim nano sudo git man-db man-pages texinfo

genfstab -U /mnt >> /mnt/etc/fstab

Inside the chroot

arch-chroot /mnt

Locale, time, hostname, user, sudo — same as Article 02 Chapter 6.

mkinitcpio.conf

BINARIES=(btrfs)
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck)
mkinitcpio -P

Swap file for hibernation (NoCOW, inside @swap)

# A swap file on Btrfs must be NoCOW or btrfs refuses to enable it as swap.
touch /swap/swapfile
chattr +C /swap/swapfile         # disable copy-on-write
chmod 600 /swap/swapfile
fallocate -l 20G /swap/swapfile  # size ≥ RAM for full hibernation
mkswap /swap/swapfile
# Don't `swapon` from inside the chroot. Add the fstab entry instead:
echo "/swap/swapfile none swap defaults 0 0" >> /etc/fstab

Find the resume offset for hibernation:

btrfs inspect-internal map-swapfile -r /swap/swapfile
# Save the printed offset — you'll paste it into the GRUB cmdline below as resume_offset=<N>.

zram-generator for everyday compressed swap

cat > /etc/systemd/zram-generator.conf <<'EOF'
[zram0]
zram-size = ram / 2
compression-algorithm = zstd
swap-priority = 100
EOF

zram comes up at boot (priority 100, higher than the swap file’s default -2, so the kernel uses zram first and falls through to the swap file only under memory pressure or hibernation).

GRUB

# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel=3 rd.luks.name=<LUKS-UUID>=cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw resume=/dev/mapper/cryptroot resume_offset=<OFFSET>"

<LUKS-UUID> is blkid /dev/nvme0n1p2 -s UUID -o value. <OFFSET> is the number printed by btrfs inspect-internal map-swapfile -r above.

grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
grub-mkconfig -o /boot/grub/grub.cfg

snapper + grub-btrfs

snapper -c root create-config /
chown :wheel /.snapshots && chmod 750 /.snapshots
systemctl enable snapper-timeline.timer snapper-cleanup.timer
systemctl enable grub-btrfsd.service

snap-pac is already installed and active — it’ll start taking pre/post snapshots around every pacman transaction automatically.

Verify, exit, reboot

exit
umount -R /mnt
cryptsetup close cryptroot
reboot

After first boot:

cryptsetup status cryptroot                                    # LUKS2 mapping active
findmnt -t btrfs -o TARGET,SOURCE,OPTIONS                       # five subvolumes mounted
swapon --show                                                   # zram0 priority 100, swap file priority -2
sudo snapper -c root list                                       # at least the post-install snapshot
sudo systemctl hibernate                                        # hibernation works (re-test after a few uses)

Next: pick a desktop layer

This tab stops where Article 03 stops in its install layer — you have an encrypted, snapshot-able, hibernation-ready single-boot Arch system, on the console. The desktop is its own piece of work, and which one you reach for depends on how much opinionation you want pre-baked:

Path A → Caelestia: a turnkey Hyprland bundle (Article 05)

Caelestia (Hyprland Desktop) — From Bare Arch to a Working Bar — PipeWire audio, GPU drivers (Intel / AMD / NVIDIA / hybrid), AUR helper, a single coherent dotfile bundle (Quickshell bar, foot, fish, themed widgets), KDE apps under Hyprland, SDDM, the wallpaper chain. Fast to “looks like a screenshot.” Read the honest retrospective at the top first — it’s opinionated and on older hardware noticeably slower than a bespoke setup.

Path B → Custom Hyprland (upcoming)

A Hyprland setup picked piece by piece — your bar, your terminal, your keybinds, your theme — with no project-bundle layer on top. Slower to “first screenshot” but every component is one you chose. Article dashed in the upcoming section on the Tech Zone landing.

Path C → Something else entirely (no article)

If you want GNOME, Plasma, sway, dwm, or anything else, skip both desktop articles above and install the relevant gnome / plasma / sway / dwm group with pacman. The install layer this tab leaves you with is desktop-agnostic — nothing about the LUKS+Btrfs+snapper stack assumes a particular compositor.


Why one article with three tabs instead of three separate articles?

The reasoning, briefly: these three procedures share ~70% of their content with Article 02 (basic install), and the parts that differ are exactly the parts a reader picking between them needs to compare side-by-side. A tab switcher does that in a way three separate articles can’t.

If you do want them as separate articles, every command above is self-contained — you can copy a tab’s content into its own file and it’ll still make sense.


Attribution

The Arch Linux logo is a trademark of the Arch Linux project and is used here under Arch Linux’s branding terms for editorial purposes only.