One of the best features of Btrfs is the ability to produce snapshots of data instantaneously. Rollbacks take advantage of Btrfs to revert the system, or any subvolume, to a previous state like before that major kernel update. This is an extremely valuable feature. Unfortunately, to take advantage of a snapshots and rollbacks properly, the filesystem must be laid out intentionally. Certain directories need to be left alone during a rollback. You don’t want to rollback your system and have your hard-work lost nor do you want to inadvertently destroy critical system logs. Unless your on openSUSE, this just isn’t done for you on most popular Linux distributions or at least not yet.[1] Even if you are using openSUSE, it doesn’t setup user home directory layouts if you wish to snapshot those. That’s why I’ve outlined my Btrfs filesystem configurations for both my system and my home directory here.

Subvolumes and Snapshots

Btrfs uses subvolumes to organize data akin to directories. Well, subvolumes are directories, practically speaking. Subvolumes have the added benefit of allowing specific Btrfs characteristics to be applied. They also provide the only method to exclude data from snapshots. When taking a snapshot of a particular subvolume, all subvolumes nested within that subvolume are excluded from the snapshot.

System Layout

The best reference for organizing your system’s subvolumes is openSUSE’s Default Subvolumes documentation. I diverge from this layout only slightly. For a complete overview of the various system directories and their purposes, the Filesystem Hierarchy Standard is your best friend.

Currently, my systems keep /boot on a separate partition so that my root filesystem can be encrypted via LUKS. In /boot, the architecture-specific Grub directories are placed in their own subvolumes. To be explicit these directories are /boot/grub/i386-pc, /boot/grub/x86_64-efi, /boot/grub/powerpc-ieee1275, and /boot/grub/s390x-emu. This is a nested layout for simplicity. For the root subvolume, I use a flat layout which allows me to use different mount options for certain subvolumes and also to provide an obvious map of the filesystem.

System Subvolumes
/

The root directory is its own subvolume.

/.snapshots

The .snapshots subvolume will contain snapshots of the root filesystem and including snapshots within snapshots is not a good idea.[2]

/home

The home directories are stored on a separate subvolume to avoid rolling back important user data.

/home/bob

Each user’s home directory is a separate subvolume so that they can be managed separately.

/opt

The /opt directory commonly contains third-party applications which should not be uninstalled during a rollback of the root filesystem.

/root

The /root directory is really just root user’s home directory and should be preserved during a rollback just like the other users' home directories.

/srv

The /srv directory contains content for web and FTP servers, so it is excluded from rollbacks.

/swap

The /swap subvolume contains the system swapfile which must be excluded from snapshots.

/tmp

It’s not necessary to save temporary files or caches in snapshots so /tmp is excluded.

/usr/local

The /usr/local directory is excluded from rollbacks to avoid uninstalling manually installed software.

/var

The /var directory contains "variable" data which equates to all sorts of things from logs and caches to virtual machine images and databases. The openSUSE project disables Copy-on-Write on this subvolume by default. I don’t because I prefer not to lose compression and checksums on everything in here, especially for log files. Instead, I disable certain features on subdirectories of /var where necessary.

/var/lib/containers

This is where Podman stores its containers, so it is given a dedicated subvolume to allow for rollbacks on this directory.

/var/lib/libvirt/images

The /var/lib/libvirt/images directory is where libvirt stores its virtual machine disk images. This directory has Copy-on-Write disabled by mounting the subvolume with the nodatacow mount option. This avoids CoW on CoW per the caution in the Debian Wiki’s page on Btrfs.

/etc/fstab

An example /etc/fstab file is provided here as a reference. Refer to the post Btrfs Mount Options for more information about the various mount options and why they are used.

/etc/fstab
/dev/mapper/sda2_crypt /                       btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=root 0 0
UUID=xxxxxxxxxxxxxxxxx /boot                   btrfs   defaults,noatime,autodefrag,compress=lzo,commit=120 0 0
/dev/mapper/sda2_crypt /.snapshots             btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=snapshots 0 0
/dev/mapper/sda2_crypt /home                   btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=home 0 0
/dev/mapper/sda2_crypt /home/bob               btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=home_bob 0 0
/dev/mapper/sda2_crypt /opt                    btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=opt 0 0
/dev/mapper/sda2_crypt /root                   btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=home_root 0 0
/dev/mapper/sda2_crypt /srv                    btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=srv 0 0
/dev/mapper/sda2_crypt /swap                   btrfs   defaults,noatime,autodefrag,commit=120,subvol=swap 0 0
/dev/mapper/sda2_crypt /tmp                    btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=tmp 0 0
/dev/mapper/sda2_crypt /usr/local              btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=usr_local 0 0
/dev/mapper/sda2_crypt /var                    btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=var 0 0
/dev/mapper/sda2_crypt /var/lib/containers     btrfs   defaults,noatime,autodefrag,compress=zstd,commit=120,subvol=var 0 0
/dev/mapper/sda2_crypt /var/lib/libvirt/images btrfs   defaults,noatime,nodatacow,commit=120,subvol=var 0 0
/swap/swapfile         none                    swap    defaults 0 0

User Layout

How to layout a user’s home directory should be considered carefully based on the desired functionality required. Do you want to be able to rollback a user’s home directory, perhaps in the case a botched configuration file won’t allow them to login? If so, it is critical to separate out the configuration and system software from the user’s important data so that such a rollback doesn’t undo hours of hard work. Do you just want to backup that important user data? Well then, go ahead and just create subvolumes for the necessary directories and forget about all the disparate directories that need to be subvolumes just so they can be excluded from snapshots of the entire home directory.

My user layout described here attempts to cover both user configuration and data within home directory snapshots, making this a bad candidate for doing rollbacks of the home directory. However, it is a good base for doing exactly that. Just separate the important directories such as Documents and Pictures into their own subvolumes and manage their snapshots separately. The layout here is a nested layout in contrast to the system’s flat layout.

The XDG Base Directory Specification and the Home Directory section from the systemd file-hierarchy(7) are the best references for the standard directories within a user’s home directory. Home directory structures can be a bit more chaotic compared to the organization of the system’s directories, though. They tend to be a bit less standardized. In addition to the standard directories, my setup accounts for per-user flatpak and AppImage applications, local virtual machine disk images, and a host of non-compliant development tooling as well my own development workflow. I choose to exclude most of these from my home directory snapshots, but you might have good reason to include some of these in your own snapshots.

User Subvolumes
.cache

Local cache files don’t need to be included in snapshots, so they aren’t.

.local

This directory contains both user-specific data directories, executables, and libraries.

.local/share/containers/storage/

Non-root Podman containers are stored in this directory for a particular user so this directory is given a dedicated subvolume in case I want to create snapshots of it at some point in the future.

.local/share/gnome-boxes/images/

This directory should have CoW disabled as it contains virtual machine disk images for GNOME Boxes. The chattr +C command can set this on the directory without the need for the mount option and this doesn’t require it be a separate subvolume within .local. I still make it a separate subvolume for good measure. You would do this like so: chattr +C ~/.local/share/gnome-boxes/images/.

.snapshots

The obligatory snapshots directory for the user’s home directory. For Snapper, this subvolume must be owned by the root user.

.var

Per-user Flatpak installations are kept in .var and so this entire directory excluded from snapshots. This is documented in the Flatpak documentation here. The config files for each application might be valuable, but I prefer to use Git to save these files out-of-band.

.xdg-non-compliant

This directory holds everything that violates the XDG specification and should be excluded from snapshots. This includes various language-specific package managers such as asdf, Cargo, and Conan. Their package caches are an obvious and unfortunate source of snapshot bloat. Creating a subvolume for each one’s default location is too much work so I configure them to reside in this directory until they are fixed to properly support the XDG Base Directory Specification.

Applications

I use AppImageLauncher to integrate AppImages with my desktop. These applications are stored in an Applications directory by default and these shouldn’t be rolled back with the home directory.

Downloads

The Downloads directory doesn’t usually contain important files but may contain large files occasionally, so I exclude it from snapshots.

Projects

I use a Projects directory for pulling down source code and building all sorts of software. While I take snapshots of this subvolume, the snapshots are kept for much shorter periods of time to avoid filling my disk with old build artifacts.

Projects/.snapshots

Of course the Projects subvolume needs its own subvolume dedicated to snapshots. For Snapper, this subvolume must be owned by the root user.

Conclusion

This post has laid out examples of Btrfs filesystem layouts. You should now have a better grasp of the various considerations in configuring a system with Btrfs. This includes what directories to exclude from snapshots by making them separate subvolumes and particular edge cases such as disk image storage for virtual machines. There are also several practical use cases here that can inform you if you have similar circumstances. Now that the ground-work is complete, next up is configuring system and user snapshots with Snapper!


1. Ubuntu does this for you with ZFS and ZSYS, but I’m talking about Btrfs here.
2. Have you seen Inception?