Create btrfs subvolumes retrospectively

This posts describes how to create subvolumes in btrfs retrospectively (with existing data).

I’m using archlinux, but most of the things should be applicable to other distors as well.
It is mostly a list of the required commands, with a tiny bit of explanations :).
All the commands are real world examples from my system, so you need to adapt device names etc. to your setup.

Move the top-level volume to a subvolume

  • create a snapshot
    1
    sudo btrfs subvol snapshot / /@

(ā€œ@ā€ seems to be a common name for the main subvolume)

  • set the new snapshot as default subvolume

    1
    2
    3
    4
    sudo btrfs subvol list /
    # outputs: ID 330 gen 1354 top level 5 path @
    # use the id from the output above to
    sudo btrfs subvol set-default 330 /
  • if it is not the root volume, skip to the last point of this section

  • adapt grub to boot from the new subvolume

    • mount the new subvolume

      1
      2
      3
      cd /mnt
      sudo mkdir newroot
      sudo mount -o subvol=@ /dev/mapper/cryptroot newroot
    • edit newroot/etc/fstab to include subvol=@ in the / options
      This should not be needed theoretically since it is the default volume, but grub-mkconfig did not produce a correct config for me without it.

      1
      2
      UUID=2240f8f3-3dfa-4aad-a62f-899f5970cb3d / btrfs defaults,subvol=@,noatime,discard,space_cache,autodefrag,compress=lzo
      0 0
    • chroot into the new subvolume and update grub

      1
      2
      3
      4
      5
      6
      7
      for i in /dev /dev/pts /proc /sys /run; do sudo mount --bind $i newroot$i; done
      sudo mount /dev/mapper/cryptboot newroot/boot
      sudo mount /dev/sdc1 newroot/boot/efi
      sudo mount --bind /sys/firmware/efi/efivars newroot/sys/firmware/efi/efivars
      sudo chroot /mnt/newroot
      grub-mkconfig -o /boot/grub/grub.cfg
      grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=arch-grub --recheck
    • unmount and reboot (at your own risk of course :D)

  • delete the old data from the toplevel

    1
    2
    3
    4
    5
    cd /mnt
    sudo mkdir oldroot
    sudo mount -o subvolid=5 /dev/mapper/cryptroot oldroot
    cd oldroot
    # CAREFULLY delete everything except @ subvolume


Move existing directory to a subvolume

In this example the directory /home/martin/develop will be replaced with a subvolume.

  • create the new subvolume

    1
    2
    3
    sodo mkdir /mnt/toplevel
    sudo mount -o subvolid=5 /dev/mapper/cryptroot /mnt/toplevel
    sudo btrfs subvol create /mnt/toplevel/@develop
  • move the data to the new subvolume in the most efficient way (as far as I know) by creating reflinks

    1
    sudo cp -ax --reflink=always /mnt/toplevel/@/home/martin/develop/. /mnt/toplevel/@develop
  • delete the old directory

    1
    2
    # DO THIS AT YOUR OWN RISK
    rm -rf /home/martin/develop
  • mount the new subvolume instead

    1
    2
    mkdir /home/martin/develop
    sudo mount -o subvol=@develop /dev/mapper/cryptroot /home/martin/develop
  • add new fstab entry to mount the new subvolume automatically

    1
    2
    UUID=2240f8f3-3dfa-4aad-a62f-899f5970cb3d /home/martin/develop btrfs defaults,subvol=@develop,noatime,discard,space_cac
    he,autodefrag,compress=lzo 0 0

Note that the top-level volume always has the ID 5.

Reference: https://btrfs.wiki.kernel.org/index.php/UseCases#Can_I_take_a_snapshot_of_a_directory.3F


Thanks for reading, I hope it was helpful to you :)