Partitioning a Multi-Boot System
The Destination
One 2TB NVMe. Three operating systems: Windows 11, Arch, and Void Linux.
/dev/nvme0n1
├── p1 Windows ntfs 72G
├── p2 Windows RE ntfs 768M
├── p3 Windows ESP fat32 256M
├── p4 MSR — 16M
├── p5 Linux ESP fat32 512M
├── p6 /boot ext4 4G
├── p7 recovery ext4 32G
├── p8 pool btrfs 1.3T
└── p9 engine xfs 512G
Nine partitions can look like over-engineering. In my case it is the opposite. Every line exists because a simpler layout failed first, and each failure pointed at the same idea: a partition is less about where files go and more about what fails together, what survives together, and what can be safely erased together.
Nobody designs this on day one, and this article won’t pretend I did. It starts where most of us start, then adds one boundary at a time, with the problem each one solves.
Where Everyone Starts
The installer’s default. Windows on one side, Linux in whatever space is left:
├── ESP (shared with Windows)
├── Windows
└── / (everything else)
This works, and that is exactly why it lingers. It works long enough to accumulate years of photos, projects, and configuration, all in one filesystem with no internal boundaries. A broken update, a failed resize, or a bad disk sector puts everything at equal risk, because the layout treats everything as equal.
The data is not equal, though. Some of it should outlive every machine you will ever own: photos, code, documents. Some is replaceable with an afternoon of effort: the OS itself. Some is replaceable with a download: packages, container images. Some is worthless the moment the process exits: caches. When all of it shares one filesystem, a problem in any category becomes a problem for every category.
That observation, how long should this data live, drives everything that follows.
Separate /home: Data Outlives the OS
The first boundary follows naturally. The operating system is the replaceable part; the home directory is not. Splitting them means they no longer share a fate:
├── / the replaceable part
└── /home the irreplaceable part
A reinstall becomes an evening task instead of a data migration. Distro-hopping becomes cheap. And once you have wiped a root partition while your data sat untouched next to it, the idea of boundaries starts to feel natural rather than paranoid.
This split also exposes the classic weakness of partitions: the sizes are guesses. A root partition that turns out too small, or a home partition that fills up while root sits half empty, means resizing filesystems, which is slow, risky, and usually happens at the worst possible time.
Btrfs: Boundaries Without Fixed Sizes
Btrfs subvolumes solve the sizing problem. A subvolume behaves like a partition in the ways that matter (it mounts separately, snapshots separately, and can be replaced separately) but all subvolumes draw from one shared pool of free space:
└── pool btrfs
├── @arch /
├── @home /home
└── @data /data
The /home boundary survives, and nobody had to predict its size. On top of that, copy-on-write makes snapshots nearly free: a read-only snapshot before every system update costs almost no space, and a broken update is undone by booting the previous snapshot.
There is a quieter benefit that matters here: when a second Linux moves in, it does not need a new partition. It is one more subvolume:
└── pool btrfs
├── @arch Arch's /
├── @void Void's /
├── @home shared by both
└── @data shared by both
Two distros, one shared home, one pool of free space. Adding an OS went from a repartitioning project to a single btrfs subvolume create.
Even Windows joins. With the WinBtrfs driver, Windows mounts the pool and keeps its data in a subvolume of its own:
└── pool btrfs
├── @arch Arch's /
├── @void Void's /
├── @win Windows' data
├── @home shared by both distros
└── @data shared by all three
This is why the Windows partition in the opening table is only 72G: it holds nothing but the operating system. Games, downloads, and documents live in @win, drawing from the same shared free space as everything else. The usual dual-boot dilemma, deciding upfront how many hundreds of gigabytes to sacrifice to NTFS, dissolves. Windows gets exactly as much space as its data actually uses, never more.
Separate /boot: Keep the Boot Path Simple
Both distros install kernels, and GRUB has to find all of them. Keeping /boot inside the btrfs pool would mean the bootloader has to understand btrfs, compression, and the subvolume layout before it can load anything. The boot path is the one part of the system that has to work precisely when everything else is broken, so it benefits from depending on as little as possible.
A small ext4 partition solves this. Both distros mount it at /boot and install their kernels into it. The GRUB configuration lives on this shared partition too, symlinked into /etc/grub.d on both systems, so neither distro owns the bootloader: either one can regenerate the menu, and both see the same entries. If one distro is broken, the other can still repair the boot configuration.
Plain ext4, no compression, no subvolumes. The dullness is deliberate: whatever state the pool is in, the machine still boots.
A Second ESP: Two Owners, Two Partitions
The default dual-boot setup places GRUB inside the EFI partition that Windows created. The problem is that Windows treats that partition as its own. Feature updates rewrite it, repair tools rebuild it, and there are plenty of reports of Linux boot entries disappearing after a Windows upgrade.
UEFI firmware has no objection to a disk having two EFI System Partitions. So Windows keeps its ESP and does whatever it wants there, and GRUB lives in a second one that Windows has no reason to touch. The cost is half a gigabyte.
The lesson generalizes: alongside asking how long data should live, it helps to ask which system writes to it. A partition with two writers tends to end up serving whichever one is more aggressive, and Windows is not shy.
Recovery: A System That Shares Nothing
Snapshots undo a bad update, but snapshots live inside the pool, and the pool itself can fail: a metadata allocation that fills up, an interrupted balance, or a mistake with my own hands. For that case there is a complete minimal Linux install on its own ext4 partition, sharing no filesystem with anything it might need to repair.
The question here is the bluntest version of the boundary idea: when the pool breaks, what still works? A rescue environment stored inside the thing it rescues answers “nothing”. 32GB on a 2TB disk felt like reasonable insurance against hunting for a live USB during an outage.
The Engine
One category of data remains, and it is the one that physically damaged this disk. Docker layers, browser caches, /var, image thumbnails, databases: data that is rewritten constantly and has no long-term value.
Btrfs is built for the opposite workload. Copy-on-write never overwrites data in place; every change writes a new copy elsewhere and updates metadata to point at it. For data that is mostly read and occasionally changed, this is what makes checksums and cheap snapshots possible. For data that is rewritten thousands of times per hour, it multiplies every write: measurements commonly show 3 to 5 times more bytes hitting the disk than the application wrote. Snapshots make it worse by keeping old copies of cache data nobody wanted to preserve.
On an NVMe drive, sustained writes are also a thermal problem. This exact workload (Immich thumbnails, Docker, ~/.cache, all on btrfs) kept my drive’s controller hot for months. The SMART log eventually showed over 6 hours of accumulated time above the critical temperature threshold, 63 media errors, and a firmware that had switched part of the drive to read-only mode. The layout was overheating the hardware.
The fix is to accept that no single filesystem fits both workloads. Btrfs stays for what benefits from checksums, compression, and snapshots. The high-churn data moves to XFS, which overwrites in place and adds no write amplification, on its own partition, bind-mounted into both distros:
└── engine xfs
├── var-arch/ → /var (Arch)
├── var-void/ → /var (Void)
├── cache/ → ~/.cache
└── docker/ → /var/lib/docker
/var deserves a closer look, because it is easy to forget how much writing happens there. It is where a Linux system does its everyday bookkeeping: logs on every event, the package manager’s database on every install, caches, spool files, container state. Almost every operation touches it, and almost none of its contents deserve a snapshot; a /var restored from last week actively conflicts with the packages installed since. It also cannot be shared the way /home can: Arch’s package database describes Arch’s packages and would be nonsense to Void. So each distro gets its own directory on the engine, bind-mounted to /var through its fstab. The two roots stay clean and snapshot-friendly in the pool, while their bookkeeping churns away on XFS.
The btrfs pool now holds only data worth snapshotting. The engine absorbs the churn, at native speed, generating no snapshot garbage and far less heat. Each filesystem does the one thing it is good at, which readers of The Arch Way will recognize as the whole philosophy in one sentence.
The Destination, Again
├── p1–p4 Windows's territory the OS only; its data lives in the pool
├── p5 Linux ESP written only by Linux
├── p6 /boot shared kernels, shared GRUB, plain ext4
├── p7 recovery depends on nothing it repairs
├── p8 pool btrfs: data worth snapshotting, shared by all three
└── p9 engine xfs: data rewritten constantly
The same table that opened this article now reads as three questions, asked of every byte on the disk:
- How long should this data live? That separates home from OS, and the pool from the engine.
- Which system writes here? That separates the Linux ESP from the Windows one, and each distro’s
/varfrom the other’s. - When something breaks, what should keep working? That separates
/bootfrom the pool, and recovery from everything.
The exact layout matters less than the questions. A different set of workloads, a different disk, or a different tolerance for risk will answer them differently, and the resulting table will look nothing like mine. What stays true is that a disk with no boundaries is not actually simpler. It is a single point of failure with a filesystem on top.
Connection closed. Returning to terminal...