Notes After Migrating to BTRFS
—
Table of Contents
In 2022 I wrote that I wanted to migrate to BTRFS on my old, trusty laptop. Since then I have migrated/installed BTRFS on many personal computers for me, my family and on servers. Here are some of my notes which I made along the process and which maybe will make someone’s life easier. I always refer to them when I setup BTRFS on a new system.
Data Migration
- I have migrated from Ext4 on top of LVM on top of LUKS to BTRFS on top of LUKS.
- Just in case I have made ordinary backup to devices which were not involved in the migration process.
-
To migrate I used external HDD which I connected via USB3. Procedure is dead simple:
-
boot into live CD
- I recommend using Debian Live KDE
- For Debian Live systems the username is user and password live. UID is 1000.
-
mount file systems: home partition under
/mnt/home
, root under/mnt/r
and external HDD under/mnt/usb
-
remove unnecessary trash from /mnt/home to speed up data migration
-
clone the whole file system to the external drive:
mkdir /mnt/usb/{root,home} rsync -axHAWXS --numeric-ids --info=progress2 /mnt/r/ /mnt/usb/root
- Note slashes at the end of rsync’s paths which change the semantics of the command. Repeat for /mnt/home. This will end up with separate directories for rootfs and home, which isn’t strictly necessary, but I like this separation.
- For ~120 GB of data cloning took ~30 minutes (and then additional 30 minutes for cloning back)
- Copying data from Ext4 to BTRFS often freezed and slowed down. It
didn’t matter which command was used (rsync, cp).
- After migration I made some tests and copying of ~30 GB of data from Ext4 to BTRFS froze whole system. It eventually unfroze.
- I don’t know the reason of freezes.
- It’s best to not plan on doing anything in parallel when copying is in progress.
-
re-partition disks:
- KDE has wonderful partition GUI program, much better than gparted, which can create encrypted partitions
umount /mnt/{r,home}
, close luks container- remove root and home partitions from the internal drive. Leave /boot and /boot/efi partitions intact.
- create a new BTRFS partition, optionally encrypted. Leave space for
swap behind it if you wish
- BTRFS has problems with swap files in multi-disk setups, so if you have many drives in your PC and plan to use swap, it’s better to create a swap partition.
- command line version of creating luks encrypted container:
cryptsetup luksFormat /dev/nvme0n1p3
- I think it’s possible to reuse the old LUKS partition by
mounting it and running
wipefs
on /dev/mapper/. I didn’t see a reason to do that and simply created new ones.
- I think it’s possible to reuse the old LUKS partition by
mounting it and running
-
create BTRFS subvolumes:
-
I prefer flat layout of subvolumes, which I find clearer and more explicit than subvolumes nested under a root subvolume. I create subvolumes for /home, /opt, /srv, /tmp, /usr/local and /var, because they all have files (either user data or important runtime data) which shouldn’t be snapshoted. This prevents data loss.
cryptsetup luksOpen /dev/nvme0n1p3 crypt_nvme0n1p3 mount /dev/mapper/crypt_nvme0n1p3 /mnt/r for subvol in @rootfs @home @opt @srv @tmp @usr_local @var; do btrfs subvolume create /mnt/r/${subvol} done btrfs subvolume create /mnt/r/@snapshots
-
we’re manually opening a luks container under a desired name. We’ll use this name in /etc/crypttab later on
- This follows openSUSE recommendations for subvolume layout.
- 2024-04-24: I usually don’t create a subvolume for /root, because I don’t use root account directly and don’t store any files there
- Default subvolume is named @rootfs because I have read somewhere that this is how Debian calls its own default subvolume. I figured that this will make it more compatible with features which Debian cooks for BTRFS.
- I’m creating additional subvolume for snapper snapshots
-
-
umount root btrfs and mount subvolumes to their target hierarchy:
mount /dev/mapper/crypt_nvme0n1p3 /mnt/r -o subvol=@rootfs,compress=zstd,noatime for subvol in home opt srv tmp usr_local var; do d=$(echo ${subvol} | sed 's|_|/|') mkdir -p /mnt/r/${d} mount /dev/mapper/crypt_nvme0n1p3 /mnt/r/${d} -o subvol=@${subvol},compress=zstd,noatime done
-
clone data back:
rsync -axHAWXS --numeric-ids --info=progress2 /mnt/usb/root/ /mnt/r rsync -axHAWXS --numeric-ids --info=progress2 /mnt/usb/home/ /mnt/r
-
in case rsync had copied some devices or other files created by the system, we may (should?) remove them
rm -rf /mnt/r/{dev,media,mnt,proc,run,sys,tmp}/*
-
at some point I disable copy-on-write for /var. This can (should?) be done on a running system:
chattr +C /var
-
Adapt crypttab and fstab
-
New partitions mean new UUIDs which must be changed in /etc/crypttab and /etc/fstab. Alternatively,
btrfstune
program, which is a part ofbtrfs-tools
, may be used to change UUIDs of offline filesystems.lsblk -o name,uuid
- remember to put UUIDs of decrypted partitions to /etc/fstab (the ones in /dev/mapper, not the encrypted ones)!
- similarily, put UUIDs of encrypted partitions in crypttab!
- Remember to put a correct name of luks container in crypttab. You
should manually use
cryptsetup luksOpen
to make sure it’ll be the same as in your target system! See “create BTRFS subvolumes” step above. -
My /etc/fstab for reference:
UUID=... / btrfs subvol=@rootfs,compress=zstd,noatime 0 1 UUID=... /boot ext2 defaults 0 2 UUID=... /boot/efi vfat umask=0077 0 1 UUID=... /home btrfs subvol=@home,compress=zstd,noatime 0 1 UUID=... /opt btrfs subvol=@opt,compress=zstd,noatime 0 1 UUID=... /srv btrfs subvol=@srv,compress=zstd,noatime 0 1 UUID=... /tmp btrfs subvol=@tmp,compress=zstd,noatime 0 1 UUID=... /usr/local btrfs subvol=@usr_local,compress=zstd,noatime 0 1 UUID=... /var btrfs subvol=@var,compress=zstd,noatime 0 1 UUID=... /.snapshots btrfs subvol=@snapshots,compress=zstd,noatime 0 1 UUID=... none swap defaults 0 0
- Fedora defaults to
0 0
for btrfs mounts, but this disables fdisk and I don’t like this. discard=async
is default since kernel 6.2. Apparently it helps to reduce read latencies. Add it to mount options if you use older kernel.
- Fedora defaults to
-
when we mount BTRFS manually, it’s easy to forget about mounting it with compression enabled and compression works only for new files. To re-compress the whole system in this case, just defragment it. Example for zstd:
btrfs filesystem defragment -r -v -czstd /
-
At some point I migrated to a bigger drive. (sidenote: With clonezilla disk-to-disk) I used KDE partition manager to resize BTRFS partition on a new drive to use it fully. KDE partition manager has good support for BTRFS and runs
btrfs
commands underneath to resize file system. Even though, it “ate” ~200 GB of space: partition had 930 GB andbtrfs filesystem usage
showed only 700 GB available. I had to resize it manually:btrfs filesystem resize max /
. -
In /etc/crypttab I resigned from the trick of unlocking many encrypted devices by passing a keyfile which resides on the first unlocked device. This trick is unsuitable for some RAIDs and multi-disk BTRFS setups which require all disks to properly mount a file system (sidenote: Maybe it works with
noearly
or some other options which I didn’t investigate.) Instead I chose to use a keyscript approach. Debian (and its derivatives I guess) ships withdecrypt_keyctl
script which caches a password for 60 seconds and passes it to all encrypted volumes in a configured group (which I called pw1), so if they share the same password, they’ll be automatically unlocked.# rootfs crypt_nvme0n1p3 UUID=... pw1 luks,discard,keyscript=decrypt_keyctl # storage crypt_sda1 UUID=... pw1 luks,discard,keyscript=decrypt_keyctl # swap crypt_nvme0n1p4 UUID=... pw1,luks,discard,keyscript=decrypt_keyctl
- You must run
update-initramfs
after adding a keyscript to crypttab.
- You must run
Chroot
-
We need to update initramfs and grub to boot our system. To do it we need to mount /boot, /dev and friends and enter chroot. We already have the rootfs in place, so it’s the matter of:
cryptsetup luksOpen /dev/nvme0n1p4 crypt_nvme0n1p4 mount /dev/nvme0n1p2 /mnt/r/boot mount /dev/nvme0n1p1 /mnt/r/boot/efi mkdir -p /mnt/r/{dev,media,mnt,proc,run,sys,tmp} for d in dev media mnt proc run sys tmp; do mount --rbind /$d /mnt/r/$d done chroot /mnt/r # in chroot: update-initramfs -u -k all update-grub
- In the above it was important to mount both /boot and /boot/efi
- I think that opening a container for swap partition is important. Anyway, I like to have the filesystem in state in which it’ll be normally.
- If you mess up for example names of luks containers, systemd might complain during boot and delay the whole boot for 90 seconds, when it tries to do impossible mounts.
Conversion from ext4
-
The following procedure worked for converting a simple ext4 filesystem on my other server in-place:
fsck.ext4 -f /dev/sda1 btrfs-convert /dev/sda1
-
To create a subvolume structure mentioned above I used a trick which doesn’t use any additional disk space (note that this removes /root - this is because on the server I have created a subvolume for /root. Don’t remove it otherwise!)
mount /dev/sda1 /mnt/r && cd /mnt/r btrfs subvolume snapshot . @rootfs cd @rootfs rm -rf /home /opt /root /srv /tmp /usr/local /var
Now for each removed directory we can create a snapshot and remove+move all unwanted contents. For example (beware of Bashism in first line!):
shopt -s extglob btrfs subvolume snapshot .. ./home cd home rm -rf -- !(home) mv home/* . ; mv home/.* . ; rmdir home
-
If /boot is on btrfs after convert, remember to reinstall it:
grub-install /dev/sda update-grub
-
I screwed this up initially, but
btrfs-convert -r /dev/sda1
worked flawlessly.
Daily maintenance
-
btrfs-assistant is a well-done GUI for btrfs management. I like using it instead of raw command line. To run it:
QT_QPA_PLATFORM=wayland btrfs-assistant-launcher
-
btrfsmaintenance is a “setup once and forget” package for periodic maintenance of BTRFS
- Its configuration is in /etc/default/btrfsmaintenance. One should edit it
and then activate with
systemctl restart btrfsmaintenance-refresh.service
, which setups several systemd timers for tasks like scrubbing and balancing of filesystem. - I did setup monthly scrubbing and balancing on my personal laptop. I disabled defragmentation and trimming.
Snapper
-
I configured snapper to make auto snapshots of @rootfs (which excludes nested subvolumes). It makes them on several occasions: on boot, before and after
apt upgrade
and once every hour. It performs auto-cleanup of old snapshots once a day.snapper -c root create-config /
- snapper is a wonderful program which you set up once and then forget about them as they do their work.
- snapper lists snapshots very slowly when BTRFS quotas are enabled.
-
snapper creates snapshots in a /.snapshots directory. It’s a good idea to create a root-level snapshots subvolume and mount it to /.snapshots via fstab, because it should ease recoveries from a grub (I didn’t have opportunity to test this mechanism yet). If you haven’t created it before:
mkdir /mnt/foo mount -o subvolid=5 /dev/... /mnt/foo cd /mnt/foo btrfs subvolume create @snapshots # and in /etc/fstab: UUID=... /.snapshots btrfs subvol=@snapshots,compress=zstd 0 1
-
if you had mounted
@snapshots
before creating snapper configuration, snapper will complain that it already exists. You must first unmount it and remove /.snapshots directory, then run snapper’screate-config
command, then unmount and remove the volume it created and then re-mount our@snapshots
(easy withmount -a
). -
grub-btrfs adds snapshots to grub, allowing to boot into snapshots
-
I disabled quota feature which apparently is responsible for a lot of slow downs:
btrfs quota disable <path>
. I did it for all of my subvolumes. -
I lowered “timeline” and “number” algorithms of snapshot cleanups to sth. like 2-4 for different times and 20 for “number”.
Fedora
- Fedora (as of version 40) supports BTRFS on installer level, but the interface for disk partitioning could be improved; it wasn’t crystal clear for me what I was doing. (sidenote: Terminology issues, maybe translation - I used Polish installer.)
- I found it the easiest to choose the “semi-automatic” approach. (sidenote: Sorry, I don’t remember the exact name - not automatic, not gparted-like partitioning - the middle one.) I let Fedora use the whole disk using “BTRFS scheme” (sidenote: I had to manually remove all existing partitions before I started the installer. IIRC this wasn’t the case in Fedora 39.) and then I add the new mountpoints. Fedora automatically creates subvolumes for them (at the root level) and assigns their names.
- Using one of immutable variants of Fedora
may remove my need for snapshots. I’ll evaluate them when I decide to move
from Debian on my main machine (probably when I’ll have to change
hardware).
- Fedora atomic projects are immutable, not reproducible (like NixOS), which is more important for me. But Fedora atomic don’t invent a language with horrible syntax.
- rpm-ostree - the one component to bind them all.