Skip to content

Instantly share code, notes, and snippets.

@minanagehsalalma
Last active April 12, 2026 20:27
Show Gist options
  • Select an option

  • Save minanagehsalalma/a2cce2ddd4702a60de234dad675a3619 to your computer and use it in GitHub Desktop.

Select an option

Save minanagehsalalma/a2cce2ddd4702a60de234dad675a3619 to your computer and use it in GitHub Desktop.

VMware Workstation Ubuntu VM Shows Huge Host Size Even Though Linux Uses Little Space

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.

Why this happens

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 .vmdk on the host can still be very large
  • snapshots and suspend files can add even more host-side bloat

Symptoms

  • 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 -k alone may reclaim almost nothing

What actually fixed it

The working fix was:

  1. Remove any VMware suspended state files if you do not need resume state.
  2. Make sure the VM is powered off and has no snapshots.
  3. Use a copy of the VM on a drive with plenty of free space.
  4. Inside Ubuntu, fill the free space with zeros.
  5. Shut the VM down cleanly.
  6. 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.

Check whether TRIM works

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.

Zero-fill method inside Ubuntu

Run this inside the guest:

sudo dd if=/dev/zero of=/zerofile bs=64M status=progress || true
sync
sudo rm -f /zerofile
sync

What 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.

Host-side shrink command

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'

Important practical notes

  • 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.

Real result from this workflow

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

Minimal repeatable workflow

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

Why fstrim was not enough here

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.

When to use a different approach

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.

Permanent cap fix used here

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 $Vmx

Inside Ubuntu, confirm the new disk name before doing anything destructive:

lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT

In 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 sync

After 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 $Vmx

Verify inside Ubuntu:

lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
df -h /
findmnt /
swapon --show

Expected 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 GB maximum VMDK deleted after boot verification
  • weekly shrink task removed because the active disk is now capped

One-line summary

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment