Contents

How to Compress qcow2 VM Images with virt-sparsify


Suppose you have a Proxmox VM with ID 100. Its disk image is located at:

/vz/images/100/vm-100-disk-0.qcow2

You suspect that the qcow2 file is larger than it should be — the VM used to have more data, but some of it was deleted. The blocks, however, were never reclaimed by the host.


Run:

qemu-img info /vz/images/100/vm-100-disk-0.qcow2

You get:

image: vm-100-disk-0.qcow2
file format: qcow2
virtual size: 30 GiB (32212254720 bytes)
disk size:    14.7 GiB
cluster_size: 65536
Format specific information:
    compat: 1.1
    compression type: zlib
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
    extended l2: false

Also check the actual file size on disk:

ls -lh /vz/images/100/vm-100-disk-0.qcow2
du -h /vz/images/100/vm-100-disk-0.qcow2

From the qemu-img info output, focus on these two lines:

virtual size: 30 GiB
disk size:    14.7 GiB

What do they mean?

  • virtual size: 30 GiB — this is the disk size the VM sees. The guest OS thinks it has a 30 GB drive.

  • disk size: 14.7 GiB — this is how much space the qcow2 file actually occupies on the Proxmox host.

If disk size is significantly smaller than virtual size — that’s normal. But if disk size is much larger than the actual data inside the VM — you have wasted space.

In our case: the VM only uses about 5 GB of real data, but the qcow2 file holds 14.7 GB. That’s ~10 GB of stale blocks that can be reclaimed.


If you see a gap between disk size and actual usage inside the VM, use virt-sparsify to zero out the unused blocks.

First, install the tool (on Proxmox host):

apt install libguestfs-tools

Stop the VM (the disk must not be in use):

qm stop 100

Run sparsify in-place:

virt-sparsify --in-place /vz/images/100/vm-100-disk-0.qcow2

Wait for completion:

Sparsify in-place operation completed with no errors

Check the image after sparsify:

qemu-img info /vz/images/100/vm-100-disk-0.qcow2

You see:

virtual size: 30 GiB
disk size:    14.7 GiB

The file size on disk did NOT change. This is important.

What virt-sparsify --in-place actually did — it zeroed out the unused blocks inside the qcow2 file. The file is still 14.7 GB, but now it contains lots of zero blocks. qcow2 doesn’t automatically shrink when blocks are zeroed — it just marks them as sparse.

To actually reduce the file size, you need the next step.


Now convert the image to a fresh qcow2 file — this will skip the zeroed blocks and create a smaller file:

qemu-img convert -O qcow2 /vz/images/100/vm-100-disk-0.qcow2 /tmp/vm-100-new.qcow2

Wait for it to finish, then replace the original:

mv /tmp/vm-100-new.qcow2 /vz/images/100/vm-100-disk-0.qcow2

Check the image one more time:

qemu-img info /vz/images/100/vm-100-disk-0.qcow2

Now you see:

image: vm-100-disk-0.qcow2
file format: qcow2
virtual size: 30 GiB (32212254720 bytes)
disk size:    4.85 GiB
cluster_size: 65536
Format specific information:
    compat: 1.1
    compression type: zlib
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
    extended l2: false

Compare:

MetricBeforeAfter
Virtual size30 GiB30 GiB
Disk size14.7 GiB4.85 GiB
Space saved~9.85 GiB (67%)

The virtual size stayed the same — the VM still sees a 30 GB disk. But the physical footprint on the host dropped from 14.7 GB to 4.85 GB.


The complete process is two commands:

# Step 1: Zero out unused blocks inside the image
virt-sparsify --in-place /vz/images/100/vm-100-disk-0.qcow2

# Step 2: Re-create the qcow2 file, skipping zeroed blocks
qemu-img convert -O qcow2 /vz/images/100/vm-100-disk-0.qcow2 /tmp/vm-100-new.qcow2 && \
mv /tmp/vm-100-new.qcow2 /vz/images/100/vm-100-disk-0.qcow2

virt-sparsify alone does NOT reduce the file size — it only marks blocks as sparse. qemu-img convert is what actually creates the smaller file.


To scan all qcow2 images on your storage and find which ones waste the most space:

for f in /vz/images/*/vm-*.qcow2; do
  echo "=== $f ==="
  qemu-img info "$f" | grep -E "virtual size|disk size"
  echo
done

Example output:

=== /vz/images/100/vm-100-disk-0.qcow2 ===
virtual size: 30 GiB (32212254720 bytes)
disk size:    14.7 GiB

=== /vz/images/101/vm-101-disk-0.qcow2 ===
virtual size: 50 GiB (53687091200 bytes)
disk size:    42.3 GiB

=== /vz/images/102/vm-102-disk-0.qcow2 ===
virtual size: 20 GiB (21474836480 bytes)
disk size:    1.2 GiB

VM 100 — clear candidate for compression. VM 102 — already optimized. Run the two-step process on each candidate.

Quick summary of total disk usage:

du -sh /vz/images/*/

StepCommandWhat it does
1virt-sparsify --in-placeZeroes out unused blocks inside qcow2
2qemu-img convert -O qcow2Creates a new, smaller qcow2 file

Important: virt-sparsify alone does NOT reduce the file size. You must run qemu-img convert after it to actually reclaim the space.

One command cleans the inside, the second command shrinks the file. Together they give you the full 67% reduction.