Shrinking the System Partition and a VM Disk Image

In the previous posts on the topic I’ve been looking at how disk images can be downloaded and uploaded to virtual machines in data centers. Depending on the data center owner, this is more or less straight forward. There is one tricky thing however, independently of the data center provider: What if the VM in the new data center has less disk space than the size of the disk image downloaded from another data center? The solution: If less space is used on the disk image than what is available in the new data center, one can shrink the system partition in the disk image. Once that is done, the size of the virtual disk image itself can be reduced. While expanding partitions and disk images is easy, shrinking them is a bit tricky. After a bit of trial and error I’ve put together the following procedure:

Main Steps

In essence, the following steps are required:

  • Partitions can’t be shrunken while they are used, so the partitions in the virtual disk image must not be used by a VM during the process. Instead, the disk file has to be attached as a virtual disk to a machine that doesn’t use these partitions for anything.
  • Once attached, the system partition in the disk image can be shrunken.
  • After this, the disk image itself can be shrunken.
  • And finally, the GPT partition headers at the beginning and end of the disk image have to be recreated because they have become invalid due to the changes.

Details

There is a certain risk that one accidentally applies some of these rather dangerous commands on the wrong partition, so I chose to run this exercise on a test notebook. So here we go, the following example shrinks a virtual disk image from 40 GB down to 30 GB:

## Commands to shrink the 40GB qcow2 disk to 30GB, including the 
# root partition inside it
####################################################################


# === 0) Variables (edit image name if needed) ===
IMG="test-disk-image.qcow2"
NBD="/dev/nbd0"
ROOT_PART="/dev/nbd0p1"  # based on a normal VM layout
FS_TARGET="26G"          # ext4 size target (must be smaller than partition target)
FINAL_SIZE="29G"         # final virtual disk size

# === 1) Backup ===
cp --reflink=auto "$IMG" "${IMG}.bak"

# === 2) Attach qcow2 via nbd ===
sudo modprobe nbd max_part=16
sudo qemu-nbd --connect="$NBD" "$IMG"
sudo partprobe "$NBD"
lsblk -o NAME,SIZE,FSTYPE,TYPE "$NBD"
sudo parted "$NBD" unit GiB print


# === 3) Shrink filesystem first (ext4) ===
sudo e2fsck -f "$ROOT_PART"
sudo resize2fs "$ROOT_PART" "$FS_TARGET"


# === 4) Shrink partition interactively (THIS is the key step) ===
sudo parted ---pretend-input-tty "$NBD" <<'EOF'
unit GiB
print
resizepart 1 28.5GiB
Yes
print
quit
EOF

# === 5) Verify partition end is below 29GiB ===
sudo parted "$NBD" unit GiB print

# === 6) Detach nbd before resizing qcow2 ===
sudo qemu-nbd --disconnect "$NBD"

# === 7) Shrink qcow2 virtual disk ===
qemu-img resize --shrink "$IMG" "$FINAL_SIZE"
qemu-img info "$IMG"


# === 8) Reattach and fix GPT backup header location ===
sudo qemu-nbd --connect="$NBD" "$IMG"
sudo sgdisk -e "$NBD"
sudo partprobe "$NBD"
sudo parted "$NBD" unit GiB print
sudo qemu-nbd --disconnect "$NBD"

While I have most things in variables at the beginning of the procedure, there is one I could not put in a variable: The new size of the system partition in step 4, which must be smaller than the final size of the disk image during this part of the procedure. Should you resize from/to different sizes, adapt the parameter accordingly.

What’s the Name of the System Partition?

In the commands above, the system partition name is /dev/nbd0p1. This might be different in other scenarios, so check this carefully after the virtual disk has been connected by using lsblk /dev/nbd0 and update ROOT_PART as required.

Expanding the System Partition Again

You might have noticed in the commands above that the system partition is shrunken to a size that is smaller than the size of the resulting disk image. This is necessary to have some empty spacing remaining at the end of the new disk image for a new backup GPT block that is created there at the end of the procedure.

In order not to loose precious disk space, the system partition can be expanded again once the disk image is used for booting a VM in the target data center. In my case, this was done automatically during startup. You can use the following command to see if it was done during first boot:

journalctl --since "24 hours ago" --no-pager | grep -Ei 'growpart|cloud-init|resize2fs|resized filesystem|vda1'

## result:

12:59:47 x-test kernel: EXT4-fs (vda1): resizing filesystem from 6815744 to 7536123 blocks
12:59:47 x-test kernel: EXT4-fs (vda1): resized filesystem to 7536123

Why so Complicated?

Yes, not a straight forward procedure but I couldn’t come up with anything simpler. If you have a simpler process, please let me know in the comments below.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.