How To

How to Configure a Filesystem Storage Pool in KVM

Two commands into the documented recipe, the build step fails with mkfs: failed to execute mkfs.auto: No such file or directory. The default source format for a KVM filesystem storage pool is auto, which works fine for mounting a device that already carries a filesystem but gives libvirt nothing to call when it has to create one. Almost every walkthrough of this pool type skips that, because almost every walkthrough formats the device by hand first and never asks libvirt to do it.

Original content from computingforgeeks.com - post 89943

This guide covers the filesystem pool type end to end with virsh: confirming hypervisor support, preparing the backing partition, defining the pool with a format libvirt can actually use, creating thin qcow2 volumes, attaching one to a running guest, and checking what comes back after the host reboots. Every command and every block of output below was run on Ubuntu 26.04 with libvirt 12.0.0 and QEMU 10.2.1 in August 2026.

Prerequisites

You need a working KVM host with libvirt running. If the hypervisor is not up yet, the KVM and QEMU install steps cover the packages and the group membership.

You also need a block device or partition you are willing to reformat. A filesystem pool consumes the whole device it is given, so nothing on it survives the build step. Size it against what the guests will actually hold: a couple of small utility VMs fit in 20 GB, while anything holding database volumes or snapshots wants 200 GB and up. The lab here uses a spare 10 GB virtual disk at /dev/vdc, which is enough to demonstrate the mechanics but well under what a real host would allocate.

Set the pool variables

The pool name, the backing device, and the mount target each appear in a dozen commands below. Export them once so the rest of the guide pastes cleanly:

export POOL_NAME="guest_fs"
export POOL_DEV="/dev/vdc1"
export POOL_TARGET="/var/lib/libvirt/guest_fs"
export POOL_FS="ext4"

These live only in the current shell. Reconnecting over SSH or jumping into sudo -i drops them, so re-export before continuing a session. Confirm they are set before anything destructive runs:

echo "Pool:   ${POOL_NAME}"
echo "Device: ${POOL_DEV}"
echo "Target: ${POOL_TARGET} (${POOL_FS})"

Confirm libvirt supports filesystem pools

The backend is compiled in on most distributions, but checking costs nothing and tells you the exact format list your build accepts:

sudo virsh pool-capabilities | grep -o "<pool type='fs' supported='yes'>"

A single matching line means the type is available:

<pool type='fs' supported='yes'>

The filesystem formats this backend accepts on Ubuntu 26.04 are auto, ext2, ext3, ext4, ufs, iso9660, udf, gfs, gfs2, vfat, hfs+, xfs, ocfs2 and vmfs. That list matters in a moment, because picking auto is what breaks the build.

Prepare the backing partition

libvirt will mount whatever device you name, so hand it a partition rather than the raw disk. A bare disk works, but a partition table makes the intent obvious to whoever inherits the host and leaves room to carve a second slice later.

sudo wipefs -a /dev/vdc
sudo parted -s /dev/vdc mklabel gpt
sudo parted -s /dev/vdc mkpart primary ext4 1MiB 100%
sudo partprobe /dev/vdc

The partition should now show up under the parent disk:

lsblk /dev/vdc

One 10 GB partition, no mount point yet:

NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vdc    253:32   0  10G  0 disk
└─vdc1 253:33   0  10G  0 part

Note the mkpart argument ext4 only writes a partition type flag. No filesystem exists on /dev/vdc1 at this point, which is exactly the state that trips the next step.

Define the pool with an explicit filesystem format

The one-liner form of pool-define-as takes the device and the mount target. Add --source-format and the whole thing works on the first pass:

sudo virsh pool-define-as "${POOL_NAME}" fs \
  --source-dev "${POOL_DEV}" \
  --source-format "${POOL_FS}" \
  --target "${POOL_TARGET}"

libvirt writes the definition to /etc/libvirt/storage/ and reports it:

Pool guest_fs defined

Dump the generated XML to see what that flag actually produced:

sudo virsh pool-dumpxml "${POOL_NAME}"

The format element under source is the piece that decides whether libvirt can format the device later:

<pool type='fs'>
  <name>guest_fs</name>
  <uuid>c128ddf1-e126-4242-9d1f-419485f19b94</uuid>
  <capacity unit='bytes'>0</capacity>
  <allocation unit='bytes'>0</allocation>
  <available unit='bytes'>0</available>
  <source>
    <device path='/dev/vdc1'/>
    <format type='ext4'/>
  </source>
  <target>
    <path>/var/lib/libvirt/guest_fs</path>
  </target>
</pool>

Prefer to keep the definition in version control? Write the same XML to a file and define from it. Open the file:

sudo vim /etc/libvirt/pools/guest_fs.xml

Only four elements are mandatory, so the hand written version is short:

<pool type='fs'>
  <name>guest_fs</name>
  <source>
    <device path='/dev/vdc1'/>
    <format type='ext4'/>
  </source>
  <target>
    <path>/var/lib/libvirt/guest_fs</path>
  </target>
</pool>

Then load it:

sudo virsh pool-define /etc/libvirt/pools/guest_fs.xml

Error: “mkfs: failed to execute mkfs.auto: No such file or directory”

Leaving --source-format off the define command sets the format to auto, and the failure only surfaces later, at build time:

error: Failed to build pool guest_fs
error: internal error: Child process (mkfs -t auto /dev/vdc1) unexpected exit status 1: mkfs: failed to execute mkfs.auto: No such file or directory

auto is a mount time instruction, not a mkfs one. It tells the kernel to probe the superblock and figure out the type on its own, which is useful when the device is already formatted and useless when it is blank. libvirt passes the value straight through to mkfs -t, and there is no mkfs.auto helper on any distribution. Fix it by undefining and redefining with a real filesystem name:

sudo virsh pool-undefine "${POOL_NAME}"
sudo virsh pool-define-as "${POOL_NAME}" fs \
  --source-dev "${POOL_DEV}" \
  --source-format ext4 \
  --target "${POOL_TARGET}"

Formatting the partition yourself with mkfs.ext4 before defining the pool also works, and auto is a reasonable choice in that case. The point is that one of the two has to know the filesystem type.

Error: “wrong fs type, bad option, bad superblock on /dev/vdc1”

This is the same root cause seen from the other end. pool-build without --overwrite succeeds, prints Pool guest_fs built, and creates nothing but the target directory. Starting the pool then fails when the mount finds no filesystem:

error: Failed to start pool guest_fs
error: internal error: Child process (mount -o nodev,nosuid,noexec -t auto /dev/vdc1 /var/lib/libvirt/guest_fs) unexpected exit status 32: mount: /var/lib/libvirt/guest_fs: wrong fs type, bad option, bad superblock on /dev/vdc1, missing codepage or helper program, or other error.

A plain pool-build on a filesystem pool is a directory creation, nothing more. That success message is honest about what it did and misleading about what you probably wanted. Only --overwrite reaches for mkfs. Confirm with blkid, which prints a partition UUID but no TYPE field while the device is still blank.

That mount line is also worth reading closely for another reason. libvirt hard codes nodev,nosuid,noexec on every filesystem pool mount, which is the right default for a directory full of disk images and a surprise if you were planning to keep helper scripts alongside them.

Build, start, and enable the pool

With a real format in the definition, --overwrite runs mkfs against the device and the pool comes up:

sudo virsh pool-build "${POOL_NAME}" --overwrite
sudo virsh pool-start "${POOL_NAME}"
sudo virsh pool-autostart "${POOL_NAME}"

Check the result. Capacity should reflect the device, and allocation should be near zero on a fresh ext4:

sudo virsh pool-info "${POOL_NAME}"

The 2 MiB of allocation is ext4 metadata, not wasted space:

Name:           guest_fs
UUID:           c128ddf1-e126-4242-9d1f-419485f19b94
State:          running
Persistent:     yes
Autostart:      yes
Capacity:       9.74 GiB
Allocation:     2.02 MiB
Available:      9.74 GiB

Here is the full sequence as it runs on the host, from define through to the mount check:

virsh pool-define-as, pool-build and pool-info output for a KVM filesystem storage pool

Two checks prove the device is genuinely mounted rather than the pool quietly writing into a local directory. The first is the mount table:

mount | grep "${POOL_TARGET}"

The ext4 mount with libvirt’s enforced options:

/dev/vdc1 on /var/lib/libvirt/guest_fs type ext4 (rw,nosuid,nodev,noexec,relatime)

The second is lost+found. A freshly created ext4 filesystem always has one, so its presence in the target directory means you are looking at the device and not at an empty folder on the root filesystem:

sudo ls -la "${POOL_TARGET}"

One directory, owned by root:

total 24
drwxr-xr-x 3 root root  4096 Aug  9 20:07 .
drwxr-xr-x 7 root root  4096 Aug  9 20:07 ..
drwx------ 2 root root 16384 Aug  9 20:07 lost+found

Create volumes

This is where the filesystem pool pulls away from its closest relative. Volumes here are ordinary files on an ordinary filesystem, so they take the name you give them and the image format you ask for:

sudo virsh vol-create-as "${POOL_NAME}" web06-data.qcow2 5G --format qcow2
sudo virsh vol-create-as "${POOL_NAME}" archive.raw 2G --format raw

Both succeed, and the details view shows the difference between the two formats:

sudo virsh vol-list "${POOL_NAME}" --details

The qcow2 volume claims 5 GiB and occupies 196 KiB. The raw volume takes its full 2 GiB immediately:

 Name               Path                                         Type   Capacity   Allocation
-----------------------------------------------------------------------------------------------
 archive.raw        /var/lib/libvirt/guest_fs/archive.raw        file   2.00 GiB   2.00 GiB
 lost+found         /var/lib/libvirt/guest_fs/lost+found         dir    0.00 B     0.00 B
 web06-data.qcow2   /var/lib/libvirt/guest_fs/web06-data.qcow2   file   5.00 GiB   196.00 KiB

The same numbers confirmed against the image itself, where qemu-img reports a 5 GiB virtual size backed by 196 KiB on disk:

virsh vol-list --details showing a 5 GiB qcow2 volume allocating only 196 KiB

Thin provisioning is the practical consequence. Seven GiB of declared volume capacity fits on a 9.74 GiB pool with room left over, because only the qcow2 clusters that hold data are ever written. Overcommit deliberately and watch Available, since the pool runs out of real space long before the guests think their disks are full.

Set against the disk pool, which hands libvirt an entire block device and carves partitions out of it, the trade is straightforward:

BehaviourFilesystem poolDisk pool
Volume is aFile on ext4/xfsPartition on the device
Custom volume namesYesNo, forced to vdb1, vdb2
qcow2 supportedYesNo, raw partitions only
Thin provisioningYes, with qcow2No, allocation equals capacity
Snapshots and backing filesYesNo
Volume limitFilesystem inode count128 GPT partitions
Space after deleting a volumeReturned to the poolLeaves a gap in the partition table

The filesystem layer costs a little throughput against raw partitions, which is why the LVM pool still has a place for latency sensitive guests, and why an iSCSI pool or an NFS export is the answer when more than one host needs the same images. For a single host that wants qcow2 on dedicated storage, the filesystem pool is the least complicated option that does the job.

Attach a volume to a running guest

Volumes on this pool attach by path, with the image format declared explicitly so QEMU does not have to guess:

sudo virsh attach-disk web02 "${POOL_TARGET}/web06-data.qcow2" vdb \
  --driver qemu --subdriver qcow2 --targetbus virtio --persistent

The --persistent flag writes the disk into the domain XML as well as the live guest, so it survives a shutdown. Verify from the host:

sudo virsh domblklist web02

The new device sits alongside the root disk and the cloud-init seed:

 Target   Source
------------------------------------------------------
 vda      /var/lib/libvirt/images/web02.qcow2
 vdb      /var/lib/libvirt/guest_fs/web06-data.qcow2
 sda      /var/lib/libvirt/images/web02-seed.iso

Inside the guest the volume is a plain virtio disk. Format and mount it the usual way:

sudo mkfs.ext4 -F /dev/vdb
sudo mkdir -p /srv/data
sudo mount /dev/vdb /srv/data
df -h /srv/data

The guest sees 4.9 GB usable out of the 5 GiB volume:

Filesystem      Size  Used Avail Use% Mounted on
/dev/vdb        4.9G  1.3M  4.6G   1% /srv/data

Back on the host, the effect of that guest side format is visible in the allocation figure. Writing an ext4 filesystem into a 5 GiB thin volume grew it from 196 KiB to 5.26 MiB, which is the only space the pool has actually given up:

sudo virsh vol-info web06-data.qcow2 --pool "${POOL_NAME}"

Capacity unchanged, allocation tracking real usage:

Name:           web06-data.qcow2
Type:           file
Capacity:       5.00 GiB
Allocation:     5.26 MiB

Delete a volume and reclaim the space

Deleting is an unlink on a filesystem, so the space comes straight back with no fragmentation to reason about:

sudo virsh vol-delete archive.raw --pool "${POOL_NAME}"
sudo virsh pool-info "${POOL_NAME}" | grep -E 'Capacity|Allocation|Available'

Removing the 2 GiB raw volume returned the pool to 9.74 GiB available immediately:

Capacity:       9.74 GiB
Allocation:     7.28 MiB
Available:      9.74 GiB

A disk pool cannot do this. Deleting a partition from the middle of its table leaves a hole that only a small enough replacement can use. If you are cycling volumes of varying sizes, that difference decides which pool type you want. The virsh command reference collects the rest of the volume subcommands, including clone and resize.

What survives a host reboot

Filesystem pools introduce a mount that no other pool type has, and that mount is invisible to the tools most admins would check. It never appears in /etc/fstab. libvirtd performs it at pool start and unmounts it at pool stop, which means the answer to “will this come back on its own” depends entirely on the autostart flag.

Rebooting the lab host with autostart enabled returned everything without intervention. The pool was active, the device mounted, and both volumes intact:

 Name            State    Autostart
-------------------------------------
 guest_fs        active   yes
 images          active   yes
 kvm-disk-pool   active   yes

The guest disk attachment survived too, because it went in with --persistent. What did not come back were the guests themselves, since domain autostart is a separate setting from pool autostart. Enable it per guest with virsh autostart web02.

Skip pool-autostart and the failure is quieter than it should be. The target directory still exists after a reboot, empty and unmounted, and any guest whose disk lives inside it refuses to start:

error: Failed to start domain 'web02'
error: Cannot access storage file '/var/lib/libvirt/guest_fs/web06-data.qcow2' (as uid:64055, gid:991): No such file or directory

Nothing is lost. Run virsh pool-start guest_fs and the guest boots normally with its data where it was. The same message appears whenever a pool is stopped for maintenance, so treat it as “the mount is missing” rather than “the image is gone” and check virsh pool-list --all before reaching for a backup. On any host where guests depend on a filesystem pool, set autostart at the same time you create it, then reboot once on purpose to prove the whole chain works before it matters. That single test is worth more than any amount of reading the pool XML, and it pairs naturally with the cloud-init provisioning workflow if you are rebuilding guests on this storage regularly.

Keep reading

Install KVM and Virt-Manager on Arch Linux Virtualization Install KVM and Virt-Manager on Arch Linux Configure Samba File Share on Debian 13 / 12 Debian Configure Samba File Share on Debian 13 / 12 Backup and Restore Linux Systems with Timeshift Debian Backup and Restore Linux Systems with Timeshift Black Friday Homelab Deals: GPUs, Mini PCs, NAS and Networking Networking Black Friday Homelab Deals: GPUs, Mini PCs, NAS and Networking Virsh Commands Cheat Sheet: Manage KVM VMs from the CLI (PDF) KVM Virsh Commands Cheat Sheet: Manage KVM VMs from the CLI (PDF) Extend Root Filesystem Using LVM on Linux KVM Extend Root Filesystem Using LVM on Linux

Leave a Comment

Press ESC to close