cd ~/>cat multi-boot-partitioning-btrfs-subvolumes.md

Multi-boot partitioning / btrfs subvolumes

#linux#btrfs#partitioning#void#arch#multiboot

What a subvolume actually is

A btrfs subvolume is a mountable namespace within a pool. It shares the same block pool as every other subvolume on the filesystem but has its own directory tree. Copy-on-write is inherited — any file in a subvolume gets btrfs’s full CoW semantics unless explicitly disabled. Each subvolume can be snapshotted independently of the others.

The practical consequence: you can have @void and @home on the same 887G partition, mount them separately, snapshot @void without touching @home, and resize either one without repartitioning. The pool’s space is shared and unallocated space is available to all subvolumes.

The @-naming convention

The @ prefix is convention, not a btrfs requirement. Its significance is GRUB: btrfs-aware GRUB scans for a subvolume named @ when looking for a Linux root. If your root subvolume is named something else, you need to specify rootflags=subvol=yourname on the kernel command line. Naming it @ makes GRUB’s default detection work without extra parameters.

The convention generalizes: @home, @var, @snapshots, and so on are recognizable to tools like snapper, Timeshift, and most distribution installers that support btrfs layouts.

The subvolume layout on p5

@void              /                    — Void root
@void-var          /var                 — Void /var, isolated from snapshots
@void-snapshots    /.snapshots          — snapper snapshot target
@home              /home                — shared between Void and Arch
@data              /data                — shared between Void and Arch
@docker            /var/lib/docker      — nodatacow, shared
@home-cache        ~/.cache             — nodatacow, per-user cache

Why @void-var is separate

/var contains package caches, logs, journal files, and Docker’s storage if Docker is not redirected elsewhere. Including /var in snapshots is mostly noise — rolling back a snapshot would revert package databases, journal entries, and build caches. It also inflates snapshot size rapidly.

Separating /var into its own subvolume means snapshots of @void capture the OS and configuration state cleanly, without dragging in ephemeral runtime data. When a snapshot is restored, /var stays as-is — package databases remain current.

Why @docker is shared

Both Void and Arch use Docker. The images are large. Keeping separate Docker storage pools for each distro would waste tens of gigabytes in duplicate layers.

The sharing is safe here because dual-boot means only one distro is running at a time. Docker’s storage backend is not designed for concurrent access from two daemons. Since the machine cannot run both distros simultaneously, there is no race. If this were a virtualization setup with both systems running concurrently, sharing would be unsafe.

@docker is mounted at /var/lib/docker in both distros’ fstab entries.

Why @home-cache is nodatacow

~/.cache holds npm packages, Rust build artifacts, browser caches, and other write-heavy transient data. CoW is expensive for this workload: every write to a cache file causes btrfs to write a new data block and update extent metadata rather than overwriting in place. For data that is neither worth snapshotting nor checksumming, CoW is pure overhead.

nodatacow disables CoW for a subvolume or file. The tradeoff: no checksums (btrfs cannot detect corruption), no CoW (no inline snapshotting). Appropriate for cache, Docker layers, VM images — anything write-heavy and recoverable.

See [Multi-boot partitioning / Cache isolation with btrfs] for how @home-cache is mounted and used.

nodatacow: chattr +C vs mount option

There are two ways to set nodatacow:

Mount optionnodatacow in fstab applies to the entire mounted subvolume. Every file created under the mount point inherits nodatacow.

chattr +C — sets the nodatacow flag on an existing file or directory. Files already in the directory do not retroactively lose CoW; only new files inherit the flag.

chattr +C ~/.cache

For @home-cache, the subvolume itself is mounted with nodatacow. The chattr +C approach is useful when you want to exempt a specific directory within a CoW subvolume — for example, disabling CoW on a VM disk image stored inside @home without affecting the rest of home.

Verify the flag:

lsattr -d ~/.cache

Output should show C in the attribute list.

What btrfs send/receive enables

btrfs send serializes a read-only snapshot to a byte stream. btrfs receive writes that stream to another btrfs filesystem. This is how subvolumes can be migrated to a new pool, backed up to a NAS over SSH, or cloned to a second disk.

btrfs subvolume snapshot -r /mnt/pool/@void /mnt/pool/@void-snap
btrfs send /mnt/pool/@void-snap | btrfs receive /mnt/target/

The practical implication: the entire Void installation is one send | receive away from being on a different disk or a remote ZFS pool. Incremental sends are also supported — after the initial transfer, only changed extents need to be transmitted.

This was the planned migration path for moving subvolumes from the old p6 partition into p5 before deleting p6. See [Multi-boot partitioning / Merging btrfs partitions].

// END OF TRANSMISSION
See you, Space Cowboy.

Connection closed. Returning to terminal...