The numbers didn’t add up
During a routine disk health check, I hit a discrepancy:
- Windows showed the C drive at 949 GB with 652 GB used — in the red.
- But
df -h /inside WSL (Ubuntu) reported only 135 GB used.
That left 500+ GB unaccounted for. Common sense says deleting a file in Linux should shrink usage on the Windows side too — I’d deleted 62 GB of video output the night before, so the C drive should have loosened up considerably. It didn’t budge.
A mismatch like that is worth digging into.
The hunt: which pocket did the space go into
Rather than deleting things blindly, I mapped out where space was actually going. The C drive is too big (600+ GB) for a full du scan to be fast, so I asked Windows directly via PowerShell, sorted by folder size:
| |
The picture was immediately clear:
| |
Drilling into C:\Users\<me>\AppData\Local:
| |
And the specific files:
| |
Two files totaling 373 GB. Culprit found.
Root cause: vhdx files only grow, never shrink
These two .vhdx files are the virtual hard disks for WSL2 and Docker Desktop. Your entire Linux system and all your Docker images ultimately live inside them.
The key is their growth strategy: grow-only.
Think of it like a closet you keep stuffing things into:
- When you write files in Linux, Windows expands the vhdx on demand, making new “shelves.”
- When you
rma file in Linux, it just marks that shelf as empty inside the closet — the closet itself (the vhdx file) doesn’t shrink at all.
That’s how you get this bizarre situation: the harder I deleted in Linux, the bigger the C drive got. Internally the vhdx already had 130 GB of “holes,” but Windows still saw the same 264 GB blob.
Same story for the Docker one — I’d just run docker system prune and cleared 20+ GB of images and build cache, yet docker_data.vhdx was still 108 GB.
The fix: manually compact to squeeze out the holes
Since it won’t shrink on its own, you compact it by hand. The idea is to mount the vhdx read-only, then use diskpart’s compact vdisk to physically squeeze out the internal holes.
Do this entirely on the Windows side, because you first need
wsl --shutdown— you can’t compact a disk while it’s spinning.
Step 1: shut down WSL and Docker
In Windows, right-click the Start menu → Terminal (Admin) / PowerShell (Admin):
| |
Step 2: compact the vhdx with diskpart
Run this for each vhdx (substitute your own path):
| |
At the DISKPART> prompt:
| |
compact vdisk runs for a few minutes (depending on file size and how many holes there are). Same for the Docker one:
| |
Can’t find your vhdx? Microsoft’s official one-liner (replace
Ubuntuwith your distro name):
1 2(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | Where-Object { $_.GetValue("DistributionName") -eq 'Ubuntu' }).GetValue("BasePath") + "\ext4.vhdx"
Actual results
On my machine:
| File | Before | After | Reclaimed |
|---|---|---|---|
WSL ext4.vhdx | 264.7G | ~140G | ~124G |
Docker docker_data.vhdx | 108.3G | ~80G | ~28G |
Free space on C jumped from 298 GB back to ~440 GB — 150 GB recovered. The process touches no Linux files; it just physically squeezes out the holes. Zero risk.
A few other bits of real junk I found along the way
- The Docker trio:
docker container prune,docker image prune -a,docker builder prune— cleared 20+ GB on this machine (dangling images + build cache). Note thatimage prune -aremoves images not currently in use, so confirm nothing you need is stopped first. - VS Code extension leftovers:
.vscode\extensionshad 27 hidden.xxxxxxxx-xxxxGUID folders — stale copies left behind every time the Codex / Claude Code extensions updated, each carrying a 100–335 MBcodex.exe/claude.exe. Clearing them freed 6.9 GB without touching the active extensions. - Package-manager caches:
~/.npm,~/.bun/install/cache,~/.cache, pip/pnpm stores — safe to delete, they rebuild automatically.
A few caveats
- Compacting a vhdx is safe, but save any unsaved work in WSL first —
wsl --shutdownkills every terminal. - If the compaction doesn’t reclaim much, run
sudo fstrim -avinside WSL first (to mark free blocks), then runcompactagain from Windows — the recovery will be more thorough. - For a permanent fix: newer WSL versions let you cap the vhdx size in
.wslconfigand enable sparse vhdx (sparseVhd=true) so the file grows on demand and gives space back more aggressively. See Microsoft’s WSL disk-space docs. - Don’t poke at WSL files under AppData with Windows Explorer/editors — you can corrupt the distro. To access Linux files from Windows, use the
\\wsl$\<distro>share instead.
Closing thought
The math turned out to be simple: the C drive wasn’t full because I had too much stuff — it was full because two virtual disks only ate and never digested. Deleting files in Linux is a “logical delete”; on the Windows vhdx it becomes “marked but never reclaimed.”
So the next time your C drive fills up and you can’t tell why, don’t reach for a reinstall — map out where the space went first. Nine times out of ten it’s some grow-only container file quietly ballooning. Find it, compact it, and the space comes back.