If your Ubuntu VM says it is using something like 27 GB, but Windows shows the VM folder taking 100+ GB, this is usually not a Linux mystery. It is a VMware virtual disk compaction issue.
VMware growable VMDKs expand when the guest writes data, but they do not automatically shrink when files are deleted inside Linux.
That means:
- Ubuntu can show low current usage
- the
.vmdkon the host can still be very large - snapshots and suspend files can add even more host-side bloat
df -h /in Ubuntu shows relatively low usage- the VM folder on Windows is much larger
- deleting files inside Ubuntu does not reduce Windows disk usage
vmware-vdiskmanager -kalone may reclaim almost nothing
The working fix was:
- Remove any VMware suspended state files if you do not need resume state.
- Make sure the VM is powered off and has no snapshots.
- Use a copy of the VM on a drive with plenty of free space.
- Inside Ubuntu, fill the free space with zeros.
- Shut the VM down cleanly.
- Run VMware host-side shrink on the VMDK.
The critical point is step 4. In this case, VMware was not exposing usable discard/TRIM support to the Ubuntu guest, so fstrim was not enough.
Inside Ubuntu:
sudo fstrim -v /If you get:
fstrim: /: the discard operation is not supported
then you should expect to use the slower zero-fill method instead.
Run this inside the guest:
sudo dd if=/dev/zero of=/zerofile bs=64M status=progress || true
sync
sudo rm -f /zerofile
syncWhat this does:
- writes zeros into the guest free space
- lets VMware identify reclaimable zeroed blocks
- prepares the VMDK for a meaningful host-side shrink
This can take a long time on large virtual disks.
After the guest is shut down:
& 'C:\Program Files (x86)\VMware\VMware Workstation\vmware-vdiskmanager.exe' -k 'C:\path\to\your.vmdk'Optional consistency check first:
& 'C:\Program Files (x86)\VMware\VMware Workstation\vmware-vdiskmanager.exe' -e 'C:\path\to\your.vmdk'- Do not run shrink while the VM is running or suspended.
- Do not rely on guest file deletion alone.
- If possible, avoid keeping long-lived snapshots.
- Suspend files can consume several GB on their own.
- Host-side shrink needs working free space on the host drive.
- If the original VM drive is too full, make a working copy on another drive first.
Example result from this case:
- original host-side VMDK: about
116.9 GB - guest real usage: about
27-28 GB - final compacted VMDK: about
34.8 GB - final VM folder size: about
32.4 GB
1. Power off VM
2. Delete suspend state if not needed
3. Boot guest
4. Zero free space with dd
5. Shut down guest cleanly
6. Run vmware-vdiskmanager -k
7. Boot once to verify
On this VM, Ubuntu reported that discard was not supported on /, so VMware was not giving the guest a useful TRIM path for reclaiming free space. Because of that, fstrim did not make the host-side .vmdk shrink in a meaningful way.
The zero-fill pass was what made the later shrink effective.
If your VM has a massively oversized virtual disk, for example a 500+ GB virtual disk even though the OS only needs 80-120 GB, compaction fixes host bloat but does not reduce the virtual disk's configured maximum size.
For that, the better long-term fix is migration to a new smaller virtual disk.
The final fix was not just compaction. The VM was migrated from the old 512 GB maximum virtual disk to a new 80 GB growable VMDK.
High-level process:
1. Create a new 80 GB VMDK.
2. Attach it as a second disk.
3. Partition it with BIOS_GRUB, EFI, and LVM partitions.
4. Create a new LVM root and swap.
5. rsync the old root filesystem to the new root.
6. Write a new fstab for the new LVM names.
7. chroot into the new root.
8. Run grub-install, update-initramfs, and update-grub.
9. Power off.
10. Make the 80 GB VMDK the primary disk.
11. Boot and verify / is on the new disk.
Host commands used on Windows:
$VmDir = 'E:\Ubuntu_22.04_VM_recovered'
$Vmx = Join-Path $VmDir 'Clone of MyPC.vmx'
$OldDisk = 'Ubuntu_22.04_VM_LinuxVMImages.COM-cl1.vmdk'
$NewDisk = 'Ubuntu_22.04_VM_LinuxVMImages.COM-80g.vmdk'
$VdiskManager = 'C:\Program Files (x86)\VMware\VMware Workstation\vmware-vdiskmanager.exe'
$Vmrun = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe'
# Create the new capped 80 GB growable VMDK.
& $VdiskManager -c -s 80GB -a lsilogic -t 0 (Join-Path $VmDir $NewDisk)
# Edit the VMX so the old disk is still the boot disk and the new disk is attached
# as a second disk. Back up the VMX first.
Copy-Item -LiteralPath $Vmx -Destination "$Vmx.before-80g-migration" -Force
$vmxText = Get-Content -LiteralPath $Vmx -Raw
$vmxText = $vmxText -replace '(?m)^scsi0:1\..*\r?\n?', ''
$insert = @"
scsi0:1.fileName = "$NewDisk"
scsi0:1.present = "TRUE"
"@
$vmxText = $vmxText -replace '(?m)^(scsi0:0\.redo = ""\r?\n)', "`$1$insert"
Set-Content -LiteralPath $Vmx -Value $vmxText -Encoding ASCII
# Boot with both disks attached.
& $Vmrun -T ws start $VmxInside Ubuntu, confirm the new disk name before doing anything destructive:
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINTIn this case, the old disk was /dev/sda and the new empty 80G disk was /dev/sdb.
Guest migration script:
#!/usr/bin/env bash
set -euxo pipefail
TARGET_DISK=/dev/sdb
TARGET_VG=vgmigrate
TARGET_ROOT_LV=root
TARGET_SWAP_LV=swap_1
TARGET_ROOT=/dev/${TARGET_VG}/${TARGET_ROOT_LV}
TARGET_SWAP=/dev/${TARGET_VG}/${TARGET_SWAP_LV}
MNT=/mnt/newroot
# WARNING: this wipes the target disk.
sudo swapoff -a || true
sudo umount -R "$MNT" || true
sudo vgchange -an "$TARGET_VG" || true
sudo vgremove -ff -y "$TARGET_VG" || true
sudo pvremove -ff -y "${TARGET_DISK}3" || true
sudo parted -s "$TARGET_DISK" mklabel gpt
sudo parted -s "$TARGET_DISK" unit MiB mkpart primary 1 2
sudo parted -s "$TARGET_DISK" set 1 bios_grub on
sudo parted -s "$TARGET_DISK" unit MiB mkpart primary fat32 2 515
sudo parted -s "$TARGET_DISK" set 2 esp on
sudo parted -s "$TARGET_DISK" unit MiB mkpart primary 515 100%
sudo mkfs.vfat -F 32 "${TARGET_DISK}2"
sudo pvcreate "${TARGET_DISK}3"
sudo vgcreate "$TARGET_VG" "${TARGET_DISK}3"
sudo lvcreate --yes --wipesignatures y -L 1G -n "$TARGET_SWAP_LV" "$TARGET_VG"
sudo lvcreate --yes --wipesignatures y -l 100%FREE -n "$TARGET_ROOT_LV" "$TARGET_VG"
sudo mkfs.ext4 -F "$TARGET_ROOT"
sudo mkswap "$TARGET_SWAP"
sudo mkdir -p "$MNT"
sudo mount "$TARGET_ROOT" "$MNT"
sudo mkdir -p "$MNT/boot/efi"
sudo mount "${TARGET_DISK}2" "$MNT/boot/efi"
sudo rsync -aAXHx --numeric-ids \
--exclude='/dev/*' \
--exclude='/proc/*' \
--exclude='/sys/*' \
--exclude='/tmp/*' \
--exclude='/run/*' \
--exclude='/mnt/*' \
--exclude='/media/*' \
--exclude='/lost+found' \
--exclude='/boot/efi/*' \
/ "$MNT/"
sudo rsync -aAXH --numeric-ids /boot/efi/ "$MNT/boot/efi/"
EFI_UUID=$(sudo blkid -s UUID -o value "${TARGET_DISK}2")
sudo tee "$MNT/etc/fstab" >/dev/null <<EOF
/dev/mapper/${TARGET_VG}-${TARGET_ROOT_LV} / ext4 errors=remount-ro 0 1
UUID=${EFI_UUID} /boot/efi vfat umask=0077 0 1
/dev/mapper/${TARGET_VG}-${TARGET_SWAP_LV} none swap sw 0 0
devpts /dev/pts devpts defaults 0 0
EOF
for dir in /dev /dev/pts /proc /sys /run; do
sudo mkdir -p "$MNT$dir"
sudo mount --bind "$dir" "$MNT$dir"
done
sudo chroot "$MNT" /bin/bash -lc 'grub-install /dev/sdb && update-initramfs -u && update-grub'
for dir in /run /sys /proc /dev/pts /dev; do
sudo umount "$MNT$dir"
done
sudo syncAfter the guest migration finishes, shut the VM down and switch the new disk to primary:
& $Vmrun -T ws stop $Vmx soft
$vmxText = Get-Content -LiteralPath $Vmx -Raw
$vmxText = $vmxText -replace 'scsi0:0\.fileName = ".*"', "scsi0:0.fileName = `"$NewDisk`""
$vmxText = $vmxText -replace '(?m)^scsi0:1\..*\r?\n?', ''
Set-Content -LiteralPath $Vmx -Value $vmxText -Encoding ASCII
& $Vmrun -T ws start $VmxVerify inside Ubuntu:
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
df -h /
findmnt /
swapon --showExpected result:
sda 80G disk
vgmigrate-root 78.5G lvm ext4 /
/dev/mapper/vgmigrate-root 77G total
Only after the new disk boots correctly should you delete the old VMDK.
Verified final state:
- active virtual disk maximum:
80 GB - active VMDK host size after migration: about
31.6 GB - Ubuntu
/mounted from/dev/mapper/vgmigrate-root - old
512 GBmaximum VMDK deleted after boot verification - weekly shrink task removed because the active disk is now capped
If a VMware Ubuntu VM is huge on the host but small inside Linux, and fstrim does not help, zero the guest free space with dd and then run vmware-vdiskmanager -k; if the virtual disk maximum is absurdly oversized, migrate to a smaller VMDK instead.