Moving many btrfs subvolumes to another disk

As I was migrating my whole system over to btrfs the other day, I came across my docker volume which was already btrfs and using subvolumes. This was of course intentional, however, I had no idea how to properly move those subvolumes to the new disk.

Surprisingly, it was somewhat easy. Btrfs has send-receive functionality which you can use to move data between disks (even do stuff like incremental backups, replication and so on. For that to work, you need to set the source subvolumes as read-only, so this usually works best with snapshots.

Example commands in instructions involve moving docker subvolumes from /var/lib/docker/btrfs/subvolumes to a new location at /mnt/new/var/lib/docker/btrfs/subvolumes:

  1. Start by making sure nothing is using the given volume(s) with lsof. Now we could make snapshots here (as they are read-only by default), but I am trying to keep it as simple as possible, let’s just set the existing volumes to read-only:
for subvol in /var/lib/docker/btrfs/subvolumes/*; do btrfs property set $subvol ro true; done
  1. Now we can send the subvolumes over to the new filesystem:
for subvol in /var/lib/docker/btrfs/subvolumes/*; do btrfs subvolume send $subvol | btrfs receive /mnt/new/var/lib/docker/btrfs/subvolumes/; done
  1. I strongly recommend verifying the sent data (with checksums) with rsync dry-run, just to be sure. The btrfs send-receive functionality is reliable though and most likely includes checksumming:
rsync -avn --checksum --delete /var/lib/docker/btrfs/subvolumes/ /mnt/new/var/lib/docker/btrfs/subvolumes/

No proposed changes should be reported here if everything went fine.

  1. Since the received volumes maintain read-only flag at destination, you have to turn it off:
for subvol in /mnt/new/var/lib/docker/btrfs/subvolumes/*; do btrfs property set $subvol ro false; done

Leave the old subvolumes read-only, to not mistakenly write to them. Unmount the old location and remount the new location in its place.