Three Single-Boot Arch Variants: Btrfs, LUKS, or Both
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:
mkinitcpio(initramfs hook configuration)cryptsetup(LUKS container management)btrfs(subvolumes, snapshots, mount options)- Arch wiki: dm-crypt/Encrypting an entire system
- Arch wiki: Btrfs
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.isoor newer) - Working network (
iwctlfor 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/nvme0n1p3Create 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 /mntThe 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/nvme0n1p2compress=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/fstabThe 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 /mntLocale, 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 -PGRUB 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.cfgSnapper + 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.servicesnap-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
rebootAfter 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 snapshotIf 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 + Btrfstab on this page). - The most-common simple pattern in 2026 is skip the swap partition entirely and use zram. Add
zram-generatorafter 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 cryptrootNow /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/cryptrootcryptroot 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/fstabThe 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 /mntLocale, 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 -PFor 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/etc/crypttab.initramfs (optional but recommended for sd-encrypt)
If you used the sd-encrypt hook above, the kernel cmdline already does the unlocking — but adding an /etc/crypttab.initramfs entry makes the configuration discoverable post-install and lets future-you read it without parsing GRUB:
# /etc/crypttab.initramfs
cryptroot UUID=<LUKS-UUID> none luks
mkinitcpio will copy it into the initramfs on the next rebuild. Re-run mkinitcpio -P if you add this.
Verify, exit, reboot
exit
umount -R /mnt
swapoff -a
cryptsetup close cryptroot
rebootExpected 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 /mntMount 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/bootPacstrap + 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/fstabInside the chroot
arch-chroot /mntLocale, 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 -PSwap 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/fstabFind 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
EOFzram 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.cfgsnapper + 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.servicesnap-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
rebootAfter 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:
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.
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.
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.
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.