Jump to content

Btrfs: Difference between revisions

From NixOS Wiki
imported>Ryan4yin
restore root subvolume via boot.initrd.postDeviceCommands
Pigs (talk | contribs)
Fix section headers, update swap file section to let nixos handle swap file creating
 
(17 intermediate revisions by 12 users not shown)
Line 1: Line 1:
'''btrfs''' is a modern copy on write (CoW) filesystem for Linux aimed at implementing advanced features while also focusing on fault tolerance, repair and easy administration.
[https://btrfs.readthedocs.io/en/latest/ btrfs] is a modern copy on write (CoW) filesystem for Linux aimed at implementing advanced features while also focusing on fault tolerance, repair and easy administration.


{{note| Use [https://github.com/nix-community/disko/ disko] to manage your NixOS storage layout declaratively. The following shows a manual approach as seen in traditional Linux distributions.}}
{{note| Use [https://github.com/nix-community/disko/ disko] to manage your NixOS storage layout declaratively. The following shows a manual approach as seen in traditional Linux distributions.}}


== Installation ==
= Installation =


{{note|The following example is for EFI enabled systems. Adjust commands accordingly for a BIOS installation.}}
{{note|The following example is for EFI enabled systems. Adjust commands accordingly for a BIOS installation.}}


=== Partition the disk ===
== Partition the disk ==
<syntaxhighlight lang="console">
<syntaxhighlight lang="console">
# printf "label: gpt\n,550M,U\n,,L\n" | sfdisk /dev/sdX
# printf "label: gpt\n,550M,U\n,,L\n" | sfdisk /dev/sdX
</syntaxhighlight>
</syntaxhighlight>


=== Format partitions and create subvolumes ===
== Format partitions and create subvolumes ==
<syntaxhighlight lang="console">
<syntaxhighlight lang="console">
# nix-shell -p btrfs-progs
# nix-shell -p btrfs-progs
Line 26: Line 26:
</syntaxhighlight>
</syntaxhighlight>


=== Mount the partitions and subvolumes ===
== Mount the partitions and subvolumes ==
<syntaxhighlight lang="console">
<syntaxhighlight lang="console">
# mount -o compress=zstd,subvol=root /dev/sdX2 /mnt
# mount -o compress=zstd,subvol=root /dev/sdX2 /mnt
Line 37: Line 37:
</syntaxhighlight>
</syntaxhighlight>


=== Install NixOS ===
== Install NixOS ==
<syntaxhighlight lang="console">
<syntaxhighlight lang="console">
# nixos-generate-config --root /mnt
# nixos-generate-config --root /mnt
# nano /mnt/etc/nixos/configuration.nix # manually add mount options
# nano /mnt/etc/nixos/configuration.nix # manually add mount options (see Compression below for an example)
# nixos-install
# nixos-install
</syntaxhighlight>
</syntaxhighlight>


== Configuration ==
= Configuration =


=== Compression ===
== Compression ==


<code>nixos-generate-config --show-hardware-config</code> doesn't detect mount options automatically, so to enable compression, you must specify it and other mount options in a persistent configuration:
<code>nixos-generate-config</code> doesn't detect mount options automatically. To enable compression, you must specify them manually and other mount options in your <code>configuration.nix</code>:


<syntaxhighlight lang="nix">
{{file|/etc/nixos/configuration.nix|nix|
<nowiki>
fileSystems = {
fileSystems = {
   "/".options = [ "compress=zstd" ];
   "/".options = [ "compress=zstd" ];
Line 57: Line 58:
   "/swap".options = [ "noatime" ];
   "/swap".options = [ "noatime" ];
};
};
</syntaxhighlight>
</nowiki>
}}
 
Btrfs supports a few compression algorithms, each with different trade-offs:
 
* <code>zstd</code>: Good compression ratio and performance, especially for general-purpose workloads. You can specify compression levels, as example <code>compress=zstd:3</code>.
 
* <code>lzo</code>: Faster but provides lower compression ratios. Good for low powered systems or where performance is important.
 
* <code>zlib</code>: Higher compression ratio but slower performance. Less commonly used nowadays in favor of zstd.
 
You can find more details on the official [https://btrfs.readthedocs.io/en/latest/Compression.html Btrfs Compression Documentation].
 
{{note| Compression is applied only to newly written data. Existing data won't be compressed unless rewritten. (e.g., <code>btrfs filesystem defrag -r -v -czstd /path</code>)}}
 
== Swap file ==


=== Swap file ===
Creating a separate subvolume for the swap file is optional. It is not required for functionality but can help with organization or snapshot management. Be sure to regenerate your <code>hardware-configuration.nix</code> if you choose to do this.


Optionally, create a separate subvolume for the swap file. Be sure to regenerate your <code>hardware-configuration.nix</code> if you choose to do this.
<syntaxhighlight lang="console">
<syntaxhighlight lang="console">
# mkdir -p /mnt
# mkdir -p /mnt
Line 68: Line 83:
# umount /mnt
# umount /mnt
# mkdir /swap
# mkdir /swap
# mount -o subvol=swap /dev/sdXY /swap
# mount -o noatime,subvol=swap /dev/sdXY /swap
# nixos-generate-config
</syntaxhighlight>
</syntaxhighlight>


Then, create the swap file and adjust its size as desired:
Finally, define a swap file in your configuration and run <code>nixos-rebuild switch</code>:
 
{{file|/etc/nixos/configuration.nix|nix|
<nowiki>
swapDevices = [{
  device = "/swap/swapfile";
  size = 8*1024; # Creates an 8GB swap file
}];
</nowiki>
}}


<syntaxhighlight lang="console">
NixOS will automatically create the swap file with the appropriate attributes for Btrfs including disabling copy on write.
# btrfs filesystem mkswapfile --size 8g --uuid clear /swap/swapfile
 
</syntaxhighlight>
{{note| On systems where you do need to manually prepare a swap file on Btrfs, you can use <code>btrfs filesystem mkswapfile</code> utility, e.g.: <br><code># btrfs filesystem mkswapfile --uuid clear /swap/swapfile</code>}}


Finally, add the swap file to your configuration and <code>nixos-rebuild switch</code>:
For more NixOS swap configuration options, see [[Swap]]. Additonal Btrfs swapfile usage can be found at [https://btrfs.readthedocs.io/en/latest/Swapfile.html the Btrfs docs].
<syntaxhighlight lang="nix">
swapDevices = [ { device = "/swap/swapfile"; } ];
</syntaxhighlight>


=== Scrubbing ===
== Scrubbing ==


Btrfs filesystem by default keeps checksums for all files, and this allows to check if contents of the file has not changed due to hardware malfunctions and other external effects.
Btrfs filesystems by default keep checksums for all files, to monitor if the file has changed due to hardware malfunctions or other external effects.


Scrubbing - is the process of checking file consistency (for this it may use checksums and/or duplicated copies of data, from raid for example). Scrubbing may be done "online", meaning you don't need to unmount a subvolume to scrub it.
Scrubbing is the process of checking file consistency, which may use checksums and/or duplicated copies of data, from raid for example. Scrubbing may be done "online", meaning you don't need to unmount a subvolume to scrub it.


You can enable automatic scrubbing with
You can enable automatic scrubbing withː
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
services.btrfs.autoScrub.enable = true;
services.btrfs.autoScrub.enable = true;
</syntaxhighlight>
</syntaxhighlight>


Automatic scrubbing by default is performed once a month, but you can change that with
Automatic scrubbing by default is performed once a month, but you can change that withː
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
services.btrfs.autoScrub.interval = "weekly";
services.btrfs.autoScrub.interval = "weekly";
Line 99: Line 121:
<code>interval</code> syntax is defined by [https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events systemd.timer's Calendar Events]
<code>interval</code> syntax is defined by [https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events systemd.timer's Calendar Events]


By default autoscrub will scrub all detected btrfs mount points, howeven in case of mounted nested subvolumes (like in example above <code>/nix</code> and <code>/home</code> are nested subvolumes under <code>/</code>) you only need to scrub the top-most one. So an example configuration may look like this:
By default, autoscrub will scrub all detected btrfs mount points. However, in case of mounted nested subvolumes (e.g. the example above where <code>/nix</code> and <code>/home</code> are nested subvolumes under <code>/</code>), you only need to scrub the topmost one. So an example configuration may look like this:
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
services.btrfs.autoScrub = {
services.btrfs.autoScrub = {
Line 108: Line 130:
</syntaxhighlight>
</syntaxhighlight>


The result of periodic auto scrub will be save to system journal, however you can also always check the status of the last scrub with
The result of the periodic auto scrub will be saved to the system journal, and you can check the status of the last scrubː
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
btrfs scrub status /
btrfs scrub status /
</syntaxhighlight>
</syntaxhighlight>


You can also start a scrubbing in background manually
You can also start a scrub in the background manuallyː
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
btrfs scrub start /
btrfs scrub start /
</syntaxhighlight>
</syntaxhighlight>
You can check the status of the ongoing scrubbing process with the same <code>status</code> command from above
You can check the status of the ongoing scrubbing process with the same <code>status</code> command as above.
 
== Deduplication ==
 
Files with (partially) equal contents can be deduplicated using [https://github.com/Zygo/bees bees] or [https://github.com/markfasheh/duperemove duperemove].
 
bees can be configured in <code>configuration.nix</code>:
 
<syntaxhighlight lang="nix">
services.beesd.filesystems = {
  root = {
    spec = "LABEL=root";
    hashTableSizeMB = 2048;
    verbosity = "crit";
    extraOptions = [ "--loadavg-target" "5.0" ];
  };
};
</syntaxhighlight>


== Usage ==
This will run the daemon in the background. To disable auto-start, use <code>systemd.services."beesd@root".wantedBy = lib.mkForce [ ];</code> for each filesystem.


=== Subvolume ===
= Usage =
 
== Subvolumes ==


Create a subvolume
Create a subvolume
Line 129: Line 170:
</syntaxhighlight>
</syntaxhighlight>


Removing a subvolume
Remove a subvolume


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 135: Line 176:
</syntaxhighlight>
</syntaxhighlight>


=== Snapshots ===
== Snapshots ==


A snapshot in btrfs is simply a subvolume that shares its data (and metadata) with some other subvolume, using btrfs's COW capabilities.
A snapshot in btrfs is simply a subvolume that shares its data (and metadata) with some other subvolume, using btrfs's CoW capabilities. Because of that, there is no special location for snapshots and you can decide where you want to store them. It can be a simple directory inside the root subvolume, or a directory inside a dedicated "snapshots" subvolume.


Because of that, there is no special location for snapshots - you need to decide where you want to store them for yourself. It can be a simple directory inside root subvolume, or a directory inside a dedicated "snapshots" subvolume.
For this example we are going to store snapshots in a directory <code>/snapshots</code>, that has to be created beforehand with <code>sudo mkdir /snapshots</code>


For this example we are going to store snapshots in a simple directory <code>/snapshots</code>, that has to be created beforehand with <code>sudo mkdir /snapshots</code>
To take a read-only (<code>-r</code>) snapshot called <code>home_snapshot_202302</code> of the subvolume mounted at <code>/home</code>
 
Taking a read-only (<code>-r</code>) snapshot called <code>home_snapshot_202302</code> of the subvolume mounted at <code>/home</code>


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 149: Line 188:
</syntaxhighlight>
</syntaxhighlight>


You can also snapshot the root subvolume. But keep in mind, that nested subvolumes are '''not''' part of a snapshot. So if you have subvolumes <code>/nix /home</code>, taking snapshot of <code>/</code> will not include them.
You can also snapshot the root subvolume. But keep in mind that nested subvolumes are '''not''' part of a snapshot. So if you have subvolumes <code>/nix /home</code>, taking a snapshot of <code>/</code> will not include them.


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 155: Line 194:
</syntaxhighlight>
</syntaxhighlight>


Make snapshot read-write again
Make snapshot read-write againː


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 161: Line 200:
</syntaxhighlight>
</syntaxhighlight>


However, changing read-only property of a snapshot in-place may [//lore.kernel.org/linux-btrfs/06e92a0b-e71b-eb21-edb5-9d2a5513b718@gmail.com/ causes issues] with any future incremental send/receive.
However, changing read-only property of a snapshot in-place may [//lore.kernel.org/linux-btrfs/06e92a0b-e71b-eb21-edb5-9d2a5513b718@gmail.com/ causes issues] with any future incremental send/receive. Instead, a read-only snapshot itself (being a simple subvolume) can be snapshotted again as a read-write snapshot like this:<syntaxhighlight lang="bash">
 
Instead, a read-only snapshot itself (being a simple subvolume) can be snapshoted again as a read-write snapshot like this:
<syntaxhighlight lang="bash">
btrfs subvolume snapshot /snapshots/home_snapshot_202302 /snapshots/home_snapshot_202302_rw
btrfs subvolume snapshot /snapshots/home_snapshot_202302 /snapshots/home_snapshot_202302_rw
</syntaxhighlight>
</syntaxhighlight>


Or it can be restored directly to <code>/home</code> straight away like this:
Or it can be restored directly to <code>/home</code> straight away like this:
{{warning|1=this will delete current <code>/home</code> and restore the snapshot! <code>/home</code> must be unmounted for this operation}}
{{warning|1=this will delete the current <code>/home</code> and restore the snapshot! <code>/home</code> must be unmounted for this operation}}


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 175: Line 211:
btrfs subvolume snapshot /snapshots/home_snapshot_202302 /home
btrfs subvolume snapshot /snapshots/home_snapshot_202302 /home
</syntaxhighlight>
</syntaxhighlight>
After this you can mount <code>/home</code> again./
After this you can mount <code>/home</code> again.
 


=== Transfer snapshot ===
== Transfer snapshot ==


Sending the snapshot <code>/snapshots/nixos_snapshot_202302</code> compressed to a remote host via ssh at <code>root@192.168.178.110</code> and saving it to a subvolume mounted or directory at <code>/mnt/nixos</code>
Sending the snapshot <code>/snapshots/nixos_snapshot_202302</code> compressed to a remote host via ssh at <code>root@192.168.178.110</code> and saving it to a subvolume mounted on a directory at <code>/mnt/nixos</code>


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
sudo btrfs send /snapshots/nixos_snapshot_202302 | zstd | ssh root@192.168.178.110 'zstd -d | btrfs receive /mnt/nixos'
sudo btrfs send /snapshots/nixos_snapshot_202302 | zstd | ssh root@192.168.178.110 'zstd -d | btrfs receive /mnt/nixos'
</syntaxhighlight>
</syntaxhighlight>
= Tips and tricks =


== Installation with encryption ==
== Installation with encryption ==
Using [https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup Luks2]:
Using [https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup Luks2]:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 194: Line 232:
</syntaxhighlight>
</syntaxhighlight>


You can use any device paritition for your bootloader # Notice that this bootloader is unencrypted on default:
You can use any device partition for your bootloader. Note that this bootloader is unencrypted by default:


<code>
<code>mkfs.vfat -n BOOT "$DISK"p1</code>
mkfs.vfat -n boot "$DISK"p1  
</code>


=== Creatings Subvolumes ===
=== Creating subvolumes ===


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 209: Line 245:
# Create the subvolumes  
# Create the subvolumes  


btrfs subvolume create /mnt/root # The subvolume for /, which will be cleared on every boot
btrfs subvolume create /mnt/root # The subvolume for /


btrfs subvolume create /mnt/home # The subvolume for /home, which should be backed up
btrfs subvolume create /mnt/home # The subvolume for /home, which should be backed up


btrfs subvolume create /mnt/nix # The subvolume for /nix, which needs to be persistent but is not worth backing up, as it’s trivial to reconstruct
btrfs subvolume create /mnt/nix # The subvolume for /nix, which needs to be persistent but is not worth backing up, as it’s trivial to reconstruct
btrfs subvolume create /mnt/persist # The subvolume for /persist, containing system state which should be persistent across reboots and possibly backed up


btrfs subvolume create /mnt/log # The subvolume for /var/log.
btrfs subvolume create /mnt/log # The subvolume for /var/log.
# Take an empty *readonly* snapshot of the root subvolume, which can be rollback to on every boot.
btrfs subvolume snapshot -r /mnt/root /mnt/root-blank
</syntaxhighlight>
</syntaxhighlight>


Add the following nix config to clear the root volume on every boot([https://grahamc.com/blog/erase-your-darlings/ Erase your darlings]):
Unmount to mount on the subvolumes for the next steps:
 
<syntaxhighlight lang="nix">
{
  boot.initrd.postDeviceCommands = lib.mkAfter ''
    mkdir /mnt
    mount -t btrfs /dev/mapper/enc /mnt
    btrfs subvolume delete /mnt/root
    btrfs subvolume snapshot /mnt/root-blank /mnt/root
  '';
}
</syntaxhighlight>


Unmount to mount on the subvolumes for the next steps:
<code>umount /mnt</code>


<code>
Once the subvolumes have been created, mount them with the desired options.
umount /mnt
</code>


Once the subvolumes has been created, mount them with the options.
Example with [https://facebook.github.io/zstd/ Zstandard compression] and noatime:
Example with [https://facebook.github.io/zstd/ Zstandard compression] with noatime:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
mount -o subvol=root,compress=zstd,noatime /dev/mapper/enc /mnt  
mount -o subvol=root,compress=zstd,noatime /dev/mapper/enc /mnt  
Line 254: Line 271:


mount -o subvol=nix,compress=zstd,noatime /dev/mapper/enc /mnt/nix
mount -o subvol=nix,compress=zstd,noatime /dev/mapper/enc /mnt/nix
mkdir /mnt/persist
mount -o subvol=persist,compress=zstd,noatime /dev/mapper/enc /mnt/persist


mkdir -p /mnt/var/log
mkdir -p /mnt/var/log
Line 268: Line 281:


mount "$DISK"p1 /mnt/boot
mount "$DISK"p1 /mnt/boot
</syntaxhighlight>
Configure <code>hardware-configuration.nix</code>
<syntaxhighlight  lang="nix">
# enable btrfs support
boot.supportedFilesystems = [ "btrfs" ];
fileSystems."/var/log" =
    { device = "/dev/disk/by-uuid/X";
      fsType = "btrfs";
      # enable noatime and zstd to the other subvolumes aswell
      options = [ "subvol=log" "compress=zstd" "noatime" ];
      # to have a correct log order
      neededForBoot = true;
    };
</syntaxhighlight >
</syntaxhighlight >
Generate Nixconfig:
Generate Nixconfig:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 275: Line 302:
</syntaxhighlight >
</syntaxhighlight >


== Convert Ext3/Ext4 system partition to Btrfs ==
<div style="border: 1px solid var(--border-color-error); background: var(--background-color-error-subtle); padding: 30px; border-radius: 5px; margin: 10px 0px; display: flex; align-items: center;">
    <div style="color: var(--border-color-error); font-size: 40px; margin-right: 15px; background: var(--background-color-error-subtle); display: flex; line-height: 0;  align-items: center;">⚠</div>
    <div style="color: var(--border-color-error); font-size: 15px; font-style: normal; font-weight: 400; line-height: normal; text-align: left;">Note that migrating your existing root filesystem can cause data loss or make your system unbootable. Make sure to backup the partition or your files. Proceed only if you know what you're doing!</div>
</div>
To convert the existing filesystem (Ext3/4) to Btrfs, boot into a NixOS live system and run the following commandː<syntaxhighlight lang="sh">
fsck -f /dev/sdXY
btrfs-convert /dev/sdXY
</syntaxhighlight>Replace the device path with the target partition. Converting larger filesystems can take a long time.
Next, mount the converted filesystem and chroot into itː<syntaxhighlight lang="sh">
mount /dev/sdXY /mnt
nixos-enter --root /mnt
</syntaxhighlight>Replace the partition UUID with the new one, which can be obtained using the command <code>blkid</code>, in <code>/etc/nixos/hardware-configuration.nix</code> and also change the filesystem to <code>btrfs</code>.<syntaxhighlight lang="nix">
  fileSystems."/" =
    { device = "/dev/disk/by-uuid/44444444-4444-4444-8888-888888888888";
      fsType = "btrfs";
    };
</syntaxhighlight>Apply the changes<syntaxhighlight lang="sh">
nixos-rebuild boot
</syntaxhighlight>If the Grub bootloader is used and it doesn't get reinstalled correctly, you can run the following command inside chrootː<syntaxhighlight lang="sh">
grub-install /dev/sdX
</syntaxhighlight>If the conversion was successful and no rollback is required, the backup image which was stored by btrfs-convert can be removed withː<syntaxhighlight lang="sh">
btrfs subvolume delete /btrfs/ext2_saved
</syntaxhighlight>
[[Category: Configuration]]
[[Category: Configuration]]
[[Category:Filesystem]]
[[Category:Filesystem]]

Latest revision as of 05:11, 22 April 2025

btrfs is a modern copy on write (CoW) filesystem for Linux aimed at implementing advanced features while also focusing on fault tolerance, repair and easy administration.

Note: Use disko to manage your NixOS storage layout declaratively. The following shows a manual approach as seen in traditional Linux distributions.

Installation

Note: The following example is for EFI enabled systems. Adjust commands accordingly for a BIOS installation.

Partition the disk

# printf "label: gpt\n,550M,U\n,,L\n" | sfdisk /dev/sdX

Format partitions and create subvolumes

# nix-shell -p btrfs-progs
# mkfs.fat -F 32 /dev/sdX1

# mkfs.btrfs /dev/sdX2
# mkdir -p /mnt
# mount /dev/sdX2 /mnt
# btrfs subvolume create /mnt/root
# btrfs subvolume create /mnt/home
# btrfs subvolume create /mnt/nix
# umount /mnt

Mount the partitions and subvolumes

# mount -o compress=zstd,subvol=root /dev/sdX2 /mnt
# mkdir /mnt/{home,nix}
# mount -o compress=zstd,subvol=home /dev/sdX2 /mnt/home
# mount -o compress=zstd,noatime,subvol=nix /dev/sdX2 /mnt/nix

# mkdir /mnt/boot
# mount /dev/sdX1 /mnt/boot

Install NixOS

# nixos-generate-config --root /mnt
# nano /mnt/etc/nixos/configuration.nix # manually add mount options (see Compression below for an example)
# nixos-install

Configuration

Compression

nixos-generate-config doesn't detect mount options automatically. To enable compression, you must specify them manually and other mount options in your configuration.nix:

/etc/nixos/configuration.nix
fileSystems = {
  "/".options = [ "compress=zstd" ];
  "/home".options = [ "compress=zstd" ];
  "/nix".options = [ "compress=zstd" "noatime" ];
  "/swap".options = [ "noatime" ];
};

Btrfs supports a few compression algorithms, each with different trade-offs:

  • zstd: Good compression ratio and performance, especially for general-purpose workloads. You can specify compression levels, as example compress=zstd:3.
  • lzo: Faster but provides lower compression ratios. Good for low powered systems or where performance is important.
  • zlib: Higher compression ratio but slower performance. Less commonly used nowadays in favor of zstd.

You can find more details on the official Btrfs Compression Documentation.

Note: Compression is applied only to newly written data. Existing data won't be compressed unless rewritten. (e.g., btrfs filesystem defrag -r -v -czstd /path)

Swap file

Creating a separate subvolume for the swap file is optional. It is not required for functionality but can help with organization or snapshot management. Be sure to regenerate your hardware-configuration.nix if you choose to do this.

# mkdir -p /mnt
# mount /dev/sdXY /mnt
# btrfs subvolume create /mnt/swap
# umount /mnt
# mkdir /swap
# mount -o noatime,subvol=swap /dev/sdXY /swap
# nixos-generate-config

Finally, define a swap file in your configuration and run nixos-rebuild switch:

/etc/nixos/configuration.nix
swapDevices = [{ 
  device = "/swap/swapfile"; 
  size = 8*1024; # Creates an 8GB swap file 
}];

NixOS will automatically create the swap file with the appropriate attributes for Btrfs including disabling copy on write.

Note: On systems where you do need to manually prepare a swap file on Btrfs, you can use btrfs filesystem mkswapfile utility, e.g.:
# btrfs filesystem mkswapfile --uuid clear /swap/swapfile

For more NixOS swap configuration options, see Swap. Additonal Btrfs swapfile usage can be found at the Btrfs docs.

Scrubbing

Btrfs filesystems by default keep checksums for all files, to monitor if the file has changed due to hardware malfunctions or other external effects.

Scrubbing is the process of checking file consistency, which may use checksums and/or duplicated copies of data, from raid for example. Scrubbing may be done "online", meaning you don't need to unmount a subvolume to scrub it.

You can enable automatic scrubbing withː

services.btrfs.autoScrub.enable = true;

Automatic scrubbing by default is performed once a month, but you can change that withː

services.btrfs.autoScrub.interval = "weekly";

interval syntax is defined by systemd.timer's Calendar Events

By default, autoscrub will scrub all detected btrfs mount points. However, in case of mounted nested subvolumes (e.g. the example above where /nix and /home are nested subvolumes under /), you only need to scrub the topmost one. So an example configuration may look like this:

services.btrfs.autoScrub = {
  enable = true;
  interval = "monthly";
  fileSystems = [ "/" ];
};

The result of the periodic auto scrub will be saved to the system journal, and you can check the status of the last scrubː

btrfs scrub status /

You can also start a scrub in the background manuallyː

btrfs scrub start /

You can check the status of the ongoing scrubbing process with the same status command as above.

Deduplication

Files with (partially) equal contents can be deduplicated using bees or duperemove.

bees can be configured in configuration.nix:

services.beesd.filesystems = {
  root = {
    spec = "LABEL=root";
    hashTableSizeMB = 2048;
    verbosity = "crit";
    extraOptions = [ "--loadavg-target" "5.0" ];
  };
};

This will run the daemon in the background. To disable auto-start, use systemd.services."beesd@root".wantedBy = lib.mkForce [ ]; for each filesystem.

Usage

Subvolumes

Create a subvolume

btrfs subvolume create /mnt/nixos

Remove a subvolume

btrfs subvolume delete /mnt/nixos

Snapshots

A snapshot in btrfs is simply a subvolume that shares its data (and metadata) with some other subvolume, using btrfs's CoW capabilities. Because of that, there is no special location for snapshots and you can decide where you want to store them. It can be a simple directory inside the root subvolume, or a directory inside a dedicated "snapshots" subvolume.

For this example we are going to store snapshots in a directory /snapshots, that has to be created beforehand with sudo mkdir /snapshots

To take a read-only (-r) snapshot called home_snapshot_202302 of the subvolume mounted at /home

btrfs subvolume snapshot -r /home /snapshots/home_snapshot_202302

You can also snapshot the root subvolume. But keep in mind that nested subvolumes are not part of a snapshot. So if you have subvolumes /nix /home, taking a snapshot of / will not include them.

btrfs subvolume snapshot -r / /snapshots/nixos_snapshot_202302

Make snapshot read-write againː

btrfs property set -ts /snapshots/home_snapshot_202302 ro false

However, changing read-only property of a snapshot in-place may causes issues with any future incremental send/receive. Instead, a read-only snapshot itself (being a simple subvolume) can be snapshotted again as a read-write snapshot like this:

btrfs subvolume snapshot /snapshots/home_snapshot_202302 /snapshots/home_snapshot_202302_rw

Or it can be restored directly to /home straight away like this:

Warning: this will delete the current /home and restore the snapshot! /home must be unmounted for this operation
btrfs subvolume delete /home
btrfs subvolume snapshot /snapshots/home_snapshot_202302 /home

After this you can mount /home again.

Transfer snapshot

Sending the snapshot /snapshots/nixos_snapshot_202302 compressed to a remote host via ssh at root@192.168.178.110 and saving it to a subvolume mounted on a directory at /mnt/nixos

sudo btrfs send /snapshots/nixos_snapshot_202302 | zstd | ssh root@192.168.178.110 'zstd -d | btrfs receive /mnt/nixos'

Tips and tricks

Installation with encryption

Using Luks2:

cryptsetup --verify-passphrase -v luksFormat "$DISK"p2 

cryptsetup open "$DISK"p2 enc

You can use any device partition for your bootloader. Note that this bootloader is unencrypted by default:

mkfs.vfat -n BOOT "$DISK"p1

Creating subvolumes

mkfs.btrfs /dev/mapper/enc # Creating btrfs partition

mount -t btrfs /dev/mapper/enc /mnt

# Create the subvolumes 

btrfs subvolume create /mnt/root # The subvolume for /

btrfs subvolume create /mnt/home # The subvolume for /home, which should be backed up

btrfs subvolume create /mnt/nix # The subvolume for /nix, which needs to be persistent but is not worth backing up, as it’s trivial to reconstruct

btrfs subvolume create /mnt/log # The subvolume for /var/log.

Unmount to mount on the subvolumes for the next steps:

umount /mnt

Once the subvolumes have been created, mount them with the desired options.

Example with Zstandard compression and noatime:

mount -o subvol=root,compress=zstd,noatime /dev/mapper/enc /mnt 

mkdir /mnt/home

mount -o subvol=home,compress=zstd,noatime /dev/mapper/enc /mnt/home

mkdir /mnt/nix

mount -o subvol=nix,compress=zstd,noatime /dev/mapper/enc /mnt/nix

mkdir -p /mnt/var/log

mount -o subvol=log,compress=zstd,noatime /dev/mapper/enc /mnt/var/log

# do not forget to create and mount the bootloader

mkdir /mnt/boot

mount "$DISK"p1 /mnt/boot

Configure hardware-configuration.nix

 # enable btrfs support
 boot.supportedFilesystems = [ "btrfs" ];

 fileSystems."/var/log" =
    { device = "/dev/disk/by-uuid/X";
      fsType = "btrfs";
      # enable noatime and zstd to the other subvolumes aswell
      options = [ "subvol=log" "compress=zstd" "noatime" ];
      # to have a correct log order
      neededForBoot = true;
    };

Generate Nixconfig:

nixos-generate-config --root /mnt

Convert Ext3/Ext4 system partition to Btrfs

Note that migrating your existing root filesystem can cause data loss or make your system unbootable. Make sure to backup the partition or your files. Proceed only if you know what you're doing!

To convert the existing filesystem (Ext3/4) to Btrfs, boot into a NixOS live system and run the following commandː

fsck -f /dev/sdXY
btrfs-convert /dev/sdXY

Replace the device path with the target partition. Converting larger filesystems can take a long time. Next, mount the converted filesystem and chroot into itː

mount /dev/sdXY /mnt
nixos-enter --root /mnt

Replace the partition UUID with the new one, which can be obtained using the command blkid, in /etc/nixos/hardware-configuration.nix and also change the filesystem to btrfs.

  fileSystems."/" =
    { device = "/dev/disk/by-uuid/44444444-4444-4444-8888-888888888888";
      fsType = "btrfs";
    };

Apply the changes

nixos-rebuild boot

If the Grub bootloader is used and it doesn't get reinstalled correctly, you can run the following command inside chrootː

grub-install /dev/sdX

If the conversion was successful and no rollback is required, the backup image which was stored by btrfs-convert can be removed withː

btrfs subvolume delete /btrfs/ext2_saved