Reclaiming Disk Space from Docker's Massive VHDX File on Windows
Notice your PC's free space vanishing? You run TreeSize (or a similar tool) and spot the culprit: a ballooning VHDX file under Docker's folder, sometimes hitting 100GB+. Docker Desktop on Windows uses WSL 2, and this dynamic virtual hard disk (VHDX) stores images, containers, and volumes—eating the free space on your C: drive.
What is WSL 2 and Why Docker Loves It
WSL 2 (Windows Subsystem for Linux 2) is Microsoft's lightweight virtual machine tech that runs a real Linux kernel directly on Windows—no heavy VM overhead. Docker Desktop switched to it by default for better performance: full Linux syscalls, faster file I/O, and seamless container isolation on your C: drive. The trade-off? That VHDX file holds the entire Linux distro state, bloating fast without maintenance.
Why the VHDX File Grows Uncontrollably
Docker's ext4.vhdx (typically at %LOCALAPPDATA%\Docker\wsl\disk\docker_data.vhdx) acts as the Linux filesystem for WSL 2. It's "dynamically expanding," growing as you pull images, run containers, or build caches—but it doesn't automatically shrink when you delete them. Unused layers linger until you prune and compact, leaving gigabytes trapped.
Step 1: Prune Docker Junk with Commands
Ensure Docker Desktop is running (check system tray). Open PowerShell as admin and run these to remove unused resources:
docker system prune -a --volumesThis removes dangling images, stopped containers, unused networks, and volumes. Follow with specifics if needed:
docker image prune -a
docker container prune
docker volume pruneCheck docker system df—you'll reclaim some space immediately.
Step 2: Optimize the VHDX to Shrink It
Pruning alone won't shrink the VHDX; compact it manually. Before optimizing, ensure you're on WSL 2.5+ for better VHDX handling:
wsl --update
Verify: wsl --version (expect 2.5.10+). Restart Docker. Newer WSL improves disk compacting.
Let's optimize! In admin PowerShell:
- Shut down WSL:
wsl --shutdown - Find the file: Usually
%LOCALAPPDATA%\Docker\wsl\disk\docker_data.vhdx(use TreeSize to confirm). - Compact:
Optimize-VHD -Path "$env:LOCALAPPDATA\Docker\wsl\disk\docker_data.vhdx" -Mode Full
It scans and reclaims unused blocks. Verify with TreeSize afterward.
Pro Tip: Set a WSL disk limit in %USERPROFILE%\.wslconfig (create if missing):
[wsl2]
memory=8GB # Caps WSL VM RAM at 8GB (default: 50% system RAM).
swap=0 # Disables swap file to avoid extra VHDX growth.
diskSize=100GB # ⚠️ Max VHDX size (default: 1TB)—choose wisely!What Happens When You Hit 100GB
WSL treats this as a hard limit. Docker builds fail with "no space left on device"—even with Windows free space. Fix by:
- Pruning Docker (
docker system prune -a --volumes) - Optimizing VHDX (
Optimize-VHD ... -Mode Full)
Start conservative (50-100GB) but monitor with docker system df.Restart WSL/Docker Desktop to apply.
Schedule monthly maintenance to prevent bloat, and here is a script - remember to edit the file location of your VHDX file!
#Requires -RunAsAdministrator
param()
$VHDX_PATH = "$env:LOCALAPPDATA\Docker\wsl\disk\docker_data.vhdx"
Write-Host "Docker Cleanup Script Starting..." -ForegroundColor Green
Write-Host "VHDX Target: $VHDX_PATH" -ForegroundColor Cyan
# Check Docker is running
try {
docker info --format '{{.ServerVersion}}' | Out-Null
Write-Host "Docker daemon running" -ForegroundColor Green
}
catch {
Write-Host "Docker Desktop not running! Start it first." -ForegroundColor Red
exit 1
}
# Show space BEFORE
Write-Host "`nSpace BEFORE prune:" -ForegroundColor Yellow
docker system df
# PRUNE (interactive)
$confirm = Read-Host "Prune ALL unused images/volumes/containers? (y/N)"
if ($confirm -match '^[Yy]$') {
Write-Host "Pruning everything..." -ForegroundColor Red
docker system prune -a --volumes -f
Write-Host "Prune complete!" -ForegroundColor Green
}
else {
Write-Host "Skipping prune" -ForegroundColor Yellow
}
# Shutdown WSL
Write-Host "`nShutting down WSL..." -ForegroundColor Yellow
wsl --shutdown
Start-Sleep -Seconds 3
# Find VHDX
if (-not (Test-Path $VHDX_PATH)) {
$altPath = "$env:LOCALAPPDATA\Docker\wsl\data\docker_data.vhdx"
if (Test-Path $altPath) {
$VHDX_PATH = $altPath
Write-Host "Found VHDX at: $VHDX_PATH" -ForegroundColor Cyan
}
else {
Write-Host "VHDX not found! Check:" -ForegroundColor Red
Write-Host " $env:LOCALAPPDATA\Docker\wsl\data\" -ForegroundColor Gray
exit 1
}
}
# OPTIMIZE VHDX
Write-Host "`nOptimizing VHDX..." -ForegroundColor Yellow
try {
$sizeBefore = (Get-Item $VHDX_PATH).Length / 1GB
Write-Host "Size before: $([math]::Round($sizeBefore,1))GB"
Optimize-VHD -Path $VHDX_PATH -Mode Full
Write-Host "VHDX optimized! Check TreeSize." -ForegroundColor Green
}
catch {
Write-Host "Optimize-VHD failed: $_" -ForegroundColor Red
}
Write-Host "`nDone! Restart Docker Desktop." -ForegroundColor Green