| |

LFCE 145 🐧 KVM Storage — Pools and Volumes

ā±ļø Estimated read: 11 min | |

KVM storage is managed through libvirt’s storage pool abstraction. A pool is a named source of storage—a directory, a physical disk, an LVM volume group, an NFS export, or an iSCSI target. A volume is a single virtual disk image within that pool. The pool defines where the storage lives; the volume is the file or block device that gets attached to a virtual machine. Without a pool, libvirt has no organized way to allocate, track, or attach disks to guests.

This chapter covers the pool types that matter for LFCE administration, the virsh commands that define and start pools, the volume creation commands for directory-based pools, and the qcow2 versus raw format decision that determines whether snapshots and thin provisioning are available.

Key point: A directory pool is the simplest and most common type. It stores volumes as files in a host directory, and the volume format (qcow2 or raw) determines whether the disk supports snapshots and sparse allocation. An LVM or iSCSI pool presents block devices, and libvirt cannot create volumes in the same way.


Why pools and volumes are separate

The abstraction problem. A virtual machine needs a disk. The disk can be a file in /var/lib/libvirt/images, an LVM logical volume, a partition on a raw device, or a LUN on an iSCSI target. Without a pool abstraction, every VM definition would need to know the exact path or device name, and moving a VM to a different storage backend would require editing the VM’s XML. The pool gives the storage a name, and the VM references the pool and volume name instead of the underlying path.

The capacity problem. A pool knows how much space it has, how much is allocated, and how much is available. virsh pool-info reports those numbers. A directory pool reports the filesystem’s free space. An LVM pool reports the volume group’s free extents. This is the information an administrator needs before creating a new volume or migrating a VM.

The lifecycle problem. A pool has a defined state: defined, built, started, and autostarted. The virsh pool-define-as command creates the configuration. virsh pool-build creates the target directory or initializes the source device. virsh pool-start activates the pool so libvirt can use it. virsh pool-autostart ensures the pool is available after a host reboot . Without the start step, a defined pool is invisible to VM creation.


a. The directory pool

The directory pool is the default and the easiest to understand. The pool’s target is a host directory, and every volume is a file inside it. libvirt creates the directory if it does not exist, and the pool reports the filesystem’s capacity and allocation.

# Define a persistent directory pool
virsh pool-define-as guest_images dir --target "/guest_images"

# Build the pool (creates the target directory)
virsh pool-build guest_images

# Start the pool
virsh pool-start guest_images

# Enable autostart
virsh pool-autostart guest_images

After the pool is started, virsh pool-list --all shows it as active, and virsh pool-info guest_images reports its state and capacity . The pool definition is stored in /etc/libvirt/storage/, and the autostart symlink is placed in /etc/libvirt/storage/autostart/ .

The default pool on most libvirt installations is a directory pool targeting /var/lib/libvirt/images. It is active and autostarted by default.


b. Creating volumes

A volume in a directory pool is a disk image file. The virsh vol-create-as command creates it with a name, a capacity, and optionally an allocation and format.

# Create a 10 GB volume with default format
virsh vol-create-as --pool guest_images --name vm1.qcow2 --capacity 10G

# Create a sparse qcow2 volume
virsh vol-create-as --pool guest_images --name vm2.qcow2 \
  --capacity 20G --allocation 0 --format qcow2

The capacity is the virtual size of the disk as the guest sees it. The allocation is the physical size on the host. When allocation is less than capacity, the volume is sparse: the host file starts small and grows as the guest writes data . This is thin provisioning at the file level, and it is how qcow2 images avoid consuming the full virtual size immediately.

The virsh vol-info command shows the capacity and allocation of an existing volume. A sparse volume shows a small allocation and a large capacity .

An alternative is to create the volume from an XML definition, which allows specifying the target path, permissions, and format explicitly. The virsh vol-create command takes the pool and the XML file .


c. qcow2 versus raw

The volume format determines the features available. The qcow2 format supports snapshots, backing files, compression, and encryption. The raw format is a direct representation of the disk contents with no metadata, and it is faster for I/O but does not support the advanced features .

Featureqcow2raw
SnapshotsYesNo (without external tools)
Backing filesYesNo
Sparse allocationYesYes (if filesystem supports it)
PerformanceLower (metadata overhead)Higher
Transfer sizeSmaller (only written sectors)Full size

A qcow2 volume is the standard choice for a VM that will be snapshotted, cloned, or used as a template. A raw volume is the choice for a VM with an I/O-intensive workload where snapshots are not needed and the image will not be transferred over the network .

The backing file feature is what makes qcow2 useful for templates. A base image is created and marked read-only, and new volumes are created with the base as a backing file. The new volume stores only the changes, and the base is shared across all the VMs created from it. This is the mechanism behind cloning and snapshot chains .


Complete Example Session

# ============================================
# PART 1: LIST EXISTING POOLS
# ============================================
virsh pool-list --all

# NAME      STATE    AUTOSTART
# default   active   yes
# ============================================
# PART 2: DEFINE A DIRECTORY POOL
# ============================================
virsh pool-define-as guest_images dir --target "/guest_images"
# ============================================
# PART 3: BUILD AND START THE POOL
# ============================================
virsh pool-build guest_images
virsh pool-start guest_images
virsh pool-autostart guest_images
# ============================================
# PART 4: VERIFY THE POOL
# ============================================
virsh pool-info guest_images

# Name:           guest_images
# UUID:           ...
# State:          running
# Persistent:     yes
# Autostart:      yes
# Capacity:       ...
# Allocation:     ...
# Available:      ...
# ============================================
# PART 5: CREATE A VOLUME
# ============================================
virsh vol-create-as --pool guest_images --name vm1.qcow2 \
  --capacity 10G --format qcow2
# ============================================
# PART 6: VERIFY THE VOLUME
# ============================================
virsh vol-info --pool guest_images vm1.qcow2

# Name:           vm1.qcow2
# Type:           file
# Capacity:       10.00 GiB
# Allocation:     196.00 KiB
# ============================================
# PART 7: CREATE A SPARSE VOLUME
# ============================================
virsh vol-create-as --pool guest_images --name vm2.qcow2 \
  --capacity 20G --allocation 0 --format qcow2
# ============================================
# PART 8: LIST VOLUMES IN THE POOL
# ============================================
virsh vol-list --pool guest_images

# Name         Path
# vm1.qcow2    /guest_images/vm1.qcow2
# vm2.qcow2    /guest_images/vm2.qcow2
# ============================================
# PART 9: THE DEFAULT POOL
# ============================================
virsh vol-list --pool default

# The default pool targets /var/lib/libvirt/images.
# ============================================
# PART 10: REMOVE A POOL
# ============================================
virsh pool-destroy guest_images
virsh pool-delete guest_images
virsh pool-undefine guest_images

The ten parts covered listing pools, defining a directory pool, building and starting it, verifying the pool, creating a volume, verifying the volume, creating a sparse volume, listing volumes, the default pool, and removing a pool.


Quick Reference

Pool lifecycle commands

CommandPurpose
virsh pool-define-as <name> <type> --target <path>Create a persistent pool definition
virsh pool-build <name>Create the target directory or initialize the source
virsh pool-start <name>Activate the pool
virsh pool-autostart <name>Start on host boot
virsh pool-list --allList all pools
virsh pool-info <name>Show pool details
virsh pool-destroy <name>Stop the pool
virsh pool-delete <name>Delete the target directory (must be empty)
virsh pool-undefine <name>Remove the configuration

Pool types

TypeSourceVolume creation
dirHost directoryFile per volume
fsFormatted filesystemFile per volume
diskPhysical disk/partitionPartition per volume
logicalLVM volume groupLogical volume per volume
netfsNFS exportFile per volume
iscsiiSCSI targetLUN per volume (pre-existing)

Volume commands

CommandPurpose
virsh vol-create-as --pool <p> --name <n> --capacity <size>Create a volume
virsh vol-create <pool> <xml>Create from XML
virsh vol-list --pool <p>List volumes
virsh vol-info --pool <p> <name>Show volume details
virsh vol-clone <name> <newname> --pool <p>Clone a volume
virsh vol-delete --pool <p> <name>Delete a volume

Format comparison

FormatSnapshotsBacking filesPerformance
qcow2YesYesLower
rawNoNoHigher

Best Practices

āœ… Do This:

# Use a directory pool for most workloads
virsh pool-define-as images dir --target /var/lib/libvirt/images  # āœ…
# Use qcow2 for VMs that will be cloned or snapshotted
virsh vol-create-as --pool images --name vm.qcow2 --format qcow2  # āœ…
# Autostart the pool so it survives a reboot
virsh pool-autostart images                                     # āœ…
# Check capacity and allocation before creating volumes
virsh pool-info images                                          # āœ…

āŒ Don’t Do This:

# Create a volume without starting the pool
virsh vol-create-as --pool images ...  # pool must be active   # āŒ
# Use raw format for a VM that needs snapshots
virsh vol-create-as ... --format raw                             # āŒ
# Delete a pool's directory without destroying the pool first
rm -rf /guest_images                                            # āŒ

Common Pitfalls

PitfallWhy It HappensFix
vol-create-as failsPool not startedvirsh pool-start <name>
Pool not available after rebootAutostart not setvirsh pool-autostart <name>
Volume allocation equals capacitySparse allocation not requestedSet --allocation 0
Snapshot failsVolume format is rawRecreate the volume as qcow2
pool-delete failsDirectory not emptyRemove volumes first
LVM pool has no volumeslibvirt cannot create LVs on all pool typesCreate the LV manually or use a directory pool

Real-World Examples

1. Define a directory pool

virsh pool-define-as images dir --target /var/lib/libvirt/images

2. Build and start

virsh pool-build images && virsh pool-start images

3. Autostart

virsh pool-autostart images

4. Create a qcow2 volume

virsh vol-create-as --pool images --name web.qcow2 --capacity 20G --format qcow2

5. Create a sparse volume

virsh vol-create-as --pool images --name db.qcow2 --capacity 50G --allocation 0 --format qcow2

6. List volumes

virsh vol-list --pool images

7. Volume info

virsh vol-info --pool images web.qcow2

8. Clone a volume

virsh vol-clone --pool images web.qcow2 web-clone.qcow2

9. Delete a volume

virsh vol-delete --pool images web-clone.qcow2

10. Remove a pool

virsh pool-destroy images && virsh pool-undefine images

Visual

The pool and volume hierarchy

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  STORAGE POOL: guest_images (dir)                            │
│    Target: /guest_images                                     │
│                                                              │
│    ā”œā”€ā”€ vm1.qcow2    (volume, 10G capacity)                  │
│    ā”œā”€ā”€ vm2.qcow2    (volume, 20G capacity, sparse)          │
│    └── template.qcow2 (volume, base image)                  │
│                                                              │
│  Each volume is a file in the pool's target directory.       │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

The pool lifecycle

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  pool-define-as ──► configuration created                   │
│       │                                                      │
│       ā–¼                                                      │
│  pool-build ──► target directory created                    │
│       │                                                      │
│       ā–¼                                                      │
│  pool-start ──► pool active, volumes can be created         │
│       │                                                      │
│       ā–¼                                                      │
│  pool-autostart ──► active after reboot                     │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

qcow2 backing files

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  base.qcow2 (read-only, shared)                              │
│       │                                                      │
│       ā”œā”€ā”€ vm1.qcow2 (backing file: base.qcow2)              │
│       │     └── stores only vm1's changes                   │
│       │                                                      │
│       └── vm2.qcow2 (backing file: base.qcow2)              │
│             └── stores only vm2's changes                   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Sparse allocation

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  capacity: 20G        allocation: 0                          │
│       │                    │                                 │
│       ā–¼                    ā–¼                                 │
│  Guest sees 20G       Host file starts near 0                │
│                       Grows as guest writes                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Summary

ItemValue
Pool abstractionNamed storage source managed by libvirt
Volume abstractionSingle disk image within a pool
Directory pool commandvirsh pool-define-as <name> dir --target <path>
Pool startvirsh pool-start <name>
Volume createvirsh vol-create-as --pool <p> --name <n> --capacity <size>
Sparse volume--allocation 0
qcow2 featuresSnapshots, backing files, compression
raw featuresPerformance, no metadata
Pool configuration location/etc/libvirt/storage/
Autostart symlink/etc/libvirt/storage/autostart/

Key takeaways:

  • A pool is a named storage source; a volume is a disk image within it. The pool abstracts the underlying directory, LVM group, or iSCSI target. The VM references the pool and volume name, not the raw path .
  • The directory pool is the default and the most flexible. It stores volumes as files in a host directory, and libvirt can create and delete volumes directly. The virsh pool-define-as, pool-build, pool-start, and pool-autostart commands create a persistent, active pool .
  • qcow2 is the format for snapshots and thin provisioning. It supports backing files, which allow a base image to be shared across multiple VMs. raw is faster but lacks those features .
  • Sparse allocation is how qcow2 avoids consuming the full virtual size. The allocation is the physical size; the capacity is the size the guest sees. Setting allocation to zero creates a thin-provisioned volume that grows as data is written .
  • The pool must be started before volumes can be created. A defined pool is a configuration, not an active resource. virsh pool-start activates it, and virsh pool-autostart ensures it is active after a reboot.
  • The default pool targets /var/lib/libvirt/images. It is a directory pool, active and autostarted on most installations. Use it for simple deployments, and create additional pools when the storage needs to be separated by workload or backend.

Remember: KVM storage is organized as pools and volumes. The pool defines where the storage lives, and the volume is the disk that gets attached to the guest. A directory pool is the simplest and most flexible. A qcow2 volume supports snapshots and thin provisioning; a raw volume prioritizes I/O performance. The pool lifecycle is define, build, start, and autostart. Without the start step, the pool is invisible to VM creation. Without the autostart step, the pool disappears after a reboot.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

šŸ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
āœ… Finance (Mortgage, Interest, Inflation)
āœ… Tech (Base64, JSON, Dev Suite, IP)
āœ… Health (BMI, BMR, TDEE)
āœ… Productivity (Timer, Workspace, QR)

āš”ļø Fast & Private
šŸ”’ No data leaves your device
šŸ’Ž 100% Free

šŸ”— Use it now: https://tools.kandz.me
šŸ”– Bookmark it—you’ll need it later!