btrfs on multiple disks: how a btrfs pool works

One filesystem across several cloud disks: the profiles, adding or growing a disk while mounted, and the usage report.

Published 27 October 2026 · By Sourabh Singh (opens in a new tab), Senior software engineer, Zoorik · Published by Zoorik

Short answer. btrfs on multiple disks is one filesystem across several block devices, and btrfs device add adds one while it’s mounted. The single profile keeps one copy of each chunk and uses every disk in full, even at different sizes. raid1 keeps two copies on two different disks, so at most half the raw space is usable.

How btrfs spans multiple disks, one chunk at a time

Picture a VM with a btrfs filesystem on one 50 GiB data disk. You attach a 100 GiB disk and run btrfs device add. Then one balance can convert the metadata, the filesystem’s record of its files, to raid1.

Of the 150 GiB, roughly 144 GiB is then usable for single data. Mirror the data with raid1, and at most 50 GiB is.

On a cloud VM, each btrfs device is usually a whole data disk: an Azure managed disk, or an Amazon Elastic Block Store (EBS) volume. Every device carries the filesystem’s UUID, which is how btrfs groups them. Where udev runs, each one is registered with the kernel as it appears, so naming any one disk mounts the whole set.

btrfs allocates space in chunks, and each new chunk goes to the disks with the most unallocated space. Traditional RAID works across whole disks of equal size. btrfs applies its RAID rules inside each chunk instead, which is why one btrfs filesystem across multiple disks of different sizes works.

Subvolumes sit on top. Each is a directory tree you can mount on its own with the subvol= option, and all share one pool of free space. That makes a btrfs storage pool useful on a VM: no mount runs out while free space sits on a disk that belongs to another.

The profile decides how much of each disk you can use

A profile sets how many copies btrfs keeps of each chunk and where it puts them. Data and metadata each get their own profile, written data/metadata: single/raid1 means one copy of your files and two of their metadata.

ProfileCopiesUsable share, equal disksFewest disksDisks it can lose
single
1
100%
1
0
dup
2, on one disk
50%
1
0
raid0
1, striped
100%
1 (kernel 5.15 or later)
0
raid1
2, on two disks
50%
2
1
raid1c3
3, on three disks
33%
3
2
raid10
2, striped
50%
2 (kernel 5.15 or later)
1

The parity profiles, raid5 and raid6, are left out on purpose. The mkfs.btrfs manual warns that they have known problems and shouldn’t be used in production.

Single data uses almost all of both disks, raid1 stops at the smaller one Two groups of bars on one scale from 0 to 100 GiB, each with disk 1 of 50 GiB above disk 2 of 100 GiB. With data single and metadata raid1, each disk starts with a metadata copy of about 3 GiB, and the rest is data, about 47 GiB on disk 1 and about 97 GiB on disk 2, so roughly 144 GiB is usable. With data raid1 and metadata raid1, disk 1 and the first 50 GiB of disk 2 hold the same chunks, metadata and data mirrored. Past 50 GiB only disk 2 has room, so its top 50 GiB is unused and at most 50 GiB is usable. The 144 GiB counts data only, while the 50 GiB includes the metadata copy. Single data uses almost all of both disks, raid1 stops at the smaller one data single, metadata raid1usable: roughly 144 GiBdisk 150 GiBdisk 2100 GiBdata, about 47 GiBdata, about 97 GiBmetadata copy, about 3 GiBdata raid1, metadata raid1usable: at most 50 GiBdisk 150 GiBdisk 2100 GiBunused, 50 GiBmirrored, metadata and datapast 50 GiB, only disk 2 has room050100GiB
Both options are drawn to one scale, from the btrfs documentation’s example. The 144 GiB counts data only. The 50 GiB includes the metadata copy.

Choosing single or raid1 data on cloud disks

An Azure locally redundant (LRS) managed disk is already written three times in one datacenter. AWS replicates each EBS volume within its Availability Zone. raid1 data adds a copy that btrfs controls, and takes two GiB of disk for every GiB of data.

Scrub reads all the data and checks its checksums. With raid1, it can repair a bad block from the other copy. With single, it finds the damage but has no copy to fix it from.

Keep metadata in raid1 either way, since it describes every file. For data, choose:

  • single data when the provider’s replication and your backups are your protection. You get every disk’s full size, and losing a disk loses the data on it.
  • raid1 data when you want scrub to repair, or the filesystem to survive losing a whole disk, such as one detached by mistake. It then mounts with the degraded option until you replace the disk.

Create one btrfs filesystem on several disks

To make btrfs span multiple disks from the start, name every disk in one command. Check each one with lsblk first, because mkfs.btrfs writes a new, empty filesystem over every device you name:

sudo mkfs.btrfs -L data -d single -m raid1 /dev/sdc /dev/sdd /dev/sde
sudo mount /dev/sdc /data

mkfs.btrfs picks profiles from the device count when you don’t name them:

  • On one device, metadata defaults to dup.
  • On several, data defaults to single and metadata to raid1.
  • Before btrfs-progs 5.8, the multi-device data default was raid0.

The manual recommends naming both profiles. That keeps the result the same across versions.

btrfs device add: add a disk to the pool without unmounting

You can add a device to the filesystem while it’s mounted. btrfs refuses a disk that already holds a filesystem. The -f flag overrides that by overwriting whatever is on the disk, so use it only on a disk you’ve confirmed is the new one.

  1. Attach a new, empty data disk to the VM. An EBS volume has to be in the instance’s Availability Zone.
  2. Find its device name with lsblk, and check that it’s the empty disk you just attached.
  3. Add it to the pool, then list, disk by disk, what each profile has allocated there:
sudo btrfs device add /dev/sdf /data
sudo btrfs device usage /data
  1. If the filesystem started on one disk, its metadata is still dup, both copies on the same disk. Convert it to raid1 with a balance. The copies then sit on different disks, and the system chunks, which map addresses to places on the disks, follow the metadata:
sudo btrfs balance start -mconvert=raid1 /data

The manual says the add itself is instant. btrfs registers the disk and moves no existing data. First, where the disk supports it, btrfs discards (TRIMs) the whole disk, unless you pass -K.

With single data, the new space is usable at once. New chunks go to the disk with the most unallocated space. While that’s the new disk, the next chunks land there.

A balance rewrites existing chunks, and the allocator places them as it places new ones, so old data spreads onto the new disk too. It’s a long job, because every chunk it moves is rewritten with its block pointers updated.

Adding a disk moves no data, and the next chunks land on it Two bars on one scale, with data in the single profile. Disk 1, 50 GiB, is mostly filled with existing chunks, which are not moved, and the rest of it is unallocated. Disk 2, 100 GiB, was just added and is all unallocated, the most room. A solid arrow takes the next data chunk into disk 2. A dashed arrow from disk 1's chunks to disk 2 shows that a balance can rewrite existing chunks onto disk 2. Disk 1's fill level and the chunk mark's size and position are illustrations. Adding a disk moves no data, and the next chunks land on it data singledisk 150 GiBdisk 2100 GiBjust addedexisting chunks, not movedunallocatedunallocated: the most roomnext data chunka balance can rewrite existing chunks onto disk 2
The two disks are drawn to one scale. Disk 1’s fill level and the chunk mark’s size and position are illustrations.

Grow one disk in place instead

Azure can expand a managed data disk without deallocating the VM, with a size limit:

  • A Standard HDD, Standard SSD or Premium SSD disk of 4 TiB or less can’t grow past 4 TiB that way. Deallocate the VM and detach the disk first.
  • Premium SSD v2 and Ultra Disk have no such limit.

AWS changes an EBS volume’s size while it’s in use. Grow it in the EC2 console or with the AWS CLI, then continue at step 3.

  1. On Azure, grow the disk. Azure rounds the size up to the nearest managed disk size.
az disk update --resource-group <resource-group> --name <disk-name> --size-gb 256
  1. Rescan the device, because Linux doesn’t see the new size until you do. Replace sdf with your disk’s name. Microsoft allows up to 10 minutes for a Premium SSD v2 or Ultra Disk to show it.
echo 1 | sudo tee /sys/class/block/sdf/device/rescan
  1. Find the devid of the disk you grew.
sudo btrfs filesystem show /data
  1. Grow that disk’s share to the whole device with resize <devid>:max. Here sdf, the pool’s fourth disk, is devid 4. Without a devid, resize acts on devid 1, which may not be the disk you grew.
sudo btrfs filesystem resize 4:max /data

resize doesn’t touch partitions. If the disk has one, grow the partition first.

Growing goes one way. Microsoft says shrinking an existing disk “isn’t supported and might result in data loss.” AWS can’t shrink an EBS volume either.

A btrfs pool gets smaller a whole disk at a time instead. btrfs device remove moves that disk’s data onto the others. It fails if the disks left can’t meet the profiles, as raid1 can’t on one disk.

Shrinking one Azure disk covers a VM without a pool.

How to read usage across a btrfs pool

Plain df gives one number for space that btrfs splits by profile. The btrfs(5) glossary says its results aren’t accurate.

btrfs filesystem usage shows the split. Its manual says it’s meant to replace btrfs filesystem df in the long run. Run it as root, since a normal user doesn’t get the per-profile detail:

sudo btrfs filesystem usage /data

The Overall section comes first:

LineWhat it counts
Device size
Raw capacity of every disk the filesystem can use, less the slack
Device allocated
Space given to data, metadata and system chunks, written or not
Device unallocated
Space in no chunk yet, which new chunks and a balance draw on
Device slack
Disk space the filesystem doesn’t use, such as a grown disk before you resize
Used
Data and metadata actually stored
Free (estimated)
An estimate of the space still usable for data
Data ratio, Metadata ratio
Raw space per byte stored: 1.0 for single, 2.0 for raid1
Multiple profiles
Whether data or metadata uses more than one profile

The Free (estimated) figure can differ from the Avail column of df, because the two are worked out differently. The min beside it is the smallest the estimate can be when more than one profile is present.

Below the Overall section, root sees each profile with its size and how full it is, then the unallocated space on each disk.

“Multiple profiles: yes” can mean a profile conversion was interrupted. btrfs(5) says to restart the same conversion balance, which carries on until every chunk has the new profile.

How Amoeba builds a btrfs pool on Azure and AWS

Amoeba optimizes block storage. It works on the data disks of running VMs: Azure managed disks and Amazon EBS volumes, on Windows and Linux VMs. It never touches the OS disk or root volume.

  • The pool. Amoeba moves a VM’s data disks onto one btrfs pool, sized to your data, with room to grow. On Azure the pool uses Premium SSD v2 where your region offers it, and Premium SSD otherwise. On AWS it uses gp3.
  • The move. Amoeba copies your data while your services run. Then it switches each mount to a pool subvolume at the same path, with a short restart of the services that use it. If the switch fails, it puts the original mounts back and tries again.
  • The old disks. Once the switch is checked, Amoeba snapshots each old disk and deletes the disk. It keeps the snapshot for a few days, then deletes it too.
  • Cost during the move. You pay for both sets of disks only while the move runs. Then you pay for the snapshots while they’re kept.
  • Rightsizing. Amoeba grows the pool while it stays mounted, before it fills, and sooner when data arrives fast. When your data goes down and stays down, it shrinks the pool one disk at a time. So you pay for close to what you use.

On Linux, the VM needs a recent kernel with btrfs. Every service that keeps a data mount open must run under systemd. The VM also needs a few free disk slots for the pool.

Sources: btrfs documentation, “btrfs-device(8)” (opens in a new tab); btrfs documentation, “mkfs.btrfs(8)” (opens in a new tab); btrfs documentation, “btrfs-filesystem(8)” (opens in a new tab); btrfs documentation, “btrfs(5)” (opens in a new tab); Linux kernel source, “fs/btrfs/volumes.c” (opens in a new tab); Microsoft Learn, “Expand virtual hard disks on a Linux VM” (opens in a new tab); Microsoft Learn, “Redundancy options for managed disks” (opens in a new tab); AWS, “Features and benefits of Amazon EBS volumes” (opens in a new tab).

Find your own number

One line in your cloud console. One PDF report with your estimated saving at list price.

Estimate your saving

Talk to the builders

Thirty minutes with the people who build Amoeba.

Book a demo (opens in a new tab)

Or write to founders@zoorik.com