There is a ready-made Ubuntu cloud image for Azure or Hyper-V, but it comes with one tiny flaw: it is a statically allocated 32 GB VHD file. To prepare it for modern deployment, the standard “download, expand, convert, shrink” workflow often ends up eating 100 GB of disk space during the process.

We can use a different approach that requires WSL2 but is much more efficient. This method allows us to change the partition size to whatever we want without bloated disk consumption.

Prepare WSL 2 environment

Run this once to install the required packages within your WSL2 environment.

# Run only once for WSL2
bash -c "sudo apt update && sudo apt install -y qemu-utils whois cloud-utils"

Get the Ubuntu image

Daily builds are available at https://cloud-images.ubuntu.com/. We will focus on 24.04 release (noble).

The following PowerShell commands will download the intended release. If you need to change the release name or the download folder, update the variables at the top of the script.

Run in PowerShell on Windows.


$tempfolder = "C:\temp\ubuvhd"
$releaseName = "noble"

# Set img names (and paths)
$ubuDestImg = "$tempfolder\$releaseName-ubuntu.img"
$ubuSrcImg = "https://cloud-images.ubuntu.com/$releaseName/current/$releaseName-server-cloudimg-arm64.img"


# Remove destination file don't fail if does not exists
Remove-Item -Path $ubuDestImg -Force -ErrorAction SilentlyContinue

# Create directory
if (-not (Test-Path $tempfolder)) 
{
    New-Item -Path $tempfolder -ItemType Directory | Out-Null
}

# Download the img
Invoke-WebRequest -Uri $ubuSrcImg -OutFile $ubuDestImg

# cd 
Set-Location $tempfolder

Convert the disk

Next, we convert the image, resize it, and mount it to WSL. Once in WSL, we will resize the partition to fill the new capacity.

Run these commands in PowerShell.

# Convert to VHD
$command = "qemu-img convert -p -f qcow2 -O vhdx $ubuVerName.img $ubuVerName.vhdx"
bash -c $command

#Resize VHD to 32GB
Resize-VHD -Path "$ubuVerName.vhdx" -SizeBytes 32GB

# wsl mount vhd
wsl --mount --vhd "$tempfolder\$ubuVerName.vhdx" --bare

Resize the Partition

We have increased the size of the VHDX, but the Linux partition is unaware of the extra space. We need to extend it. Usually, the Linux partition is the first one ( /dev/sdd1 in my case)

Optionally you can run filesystem checker.

After resizing, we unmount the drive from WSL.

# Execute to find the mounted disk (one with three patitions, sdd in my case)
bash -c "lsblk"

# Resize the partition to fill the disk
bash -c "sudo growpart /dev/sdd 1"

#check the file system (optional but recommended)
bash -c "sudo e2fsck -f /dev/sdd1"

# umount from wsl
wsl --unmount "$tempfolder\$ubuVerName.vhdx"

As an optional step, you can create a symbolic link (ubuntu-latest.vhdx) pointing to your newly created imageubuntu-ubuReleaseName.vhdx.

# Set symbolic link latest to converted vhdx
$latestLink = "$tempfolder\ubuntu-latest.vhdx"
if (Test-Path $latestLink) {
    Remove-Item -Path $latestLink -Force
}
New-Item -ItemType SymbolicLink -Path $latestLink -Target "$tempfolder\$ubuVerName.vhdx" | Out-Null

Conclusion

We have successfully created a compact Hyper-V Ubuntu VHDX without wasting massive amounts of temporary disk space.

One thing to keep in mind: the original large VHD images provided by Ubuntu often use an Azure-optimized kernel (which has Hyper-V tweaks compiled in). Since we used the raw cloud image, you may want to switch to the optimized kernel manually after deployment if you need those specific performance tweaks.