Developer Tools

WSL2 Disk Space Recovery: Automating VHDX Compaction

By DexNox Dev Team Published May 4, 2026

Windows Subsystem for Linux 2 (WSL2) virtual disk structures expand automatically as data is written. However, deleting files within the Linux guest does not trigger compaction routines on the Windows host. Over months of software development (involving pulling Docker layers, compiling packages, and downloading dependencies), a virtual disk file can consume 80GB of host storage while only housing 20GB of actual data. Compacting the virtual hard disk (ext4.vhdx) allows you to reclaim this unused space.

The Mechanics of Dynamic VHDX Growth

WSL2 stores its Linux distribution data inside a Virtual Hard Disk v2 (VHDX) format file. Hyper-V allocates these disks as dynamic (sparse) expand-on-write drives:

  1. Logical Sector Mapping: The guest Linux kernel interacts with the virtual drive using standard ext4 logic.
  2. Block Allocation Table (BAT): The hypervisor maps physical VHDX sectors dynamically. As new blocks are written, the VHDX file expands on the host disk in 1MB chunks.
  3. The Disconnect: When you delete a file inside Linux, the guest OS marks the sectors as free within its internal ext4 block map. However, because the hypervisor has no awareness of guest filesystem modifications, the host VHDX file remains at its peak size.
Host Disk VHDX File Size (Peak Allocation e.g., 80GB)
    ├── Active Guest Data (Occupied ext4 sectors e.g., 20GB)
    └── Unused Allocated Block space (Marked free in Linux e.g., 60GB)

This dynamic allocation differs from fixed-size virtual disks, which pre-allocate their full logical size on the host file system. While dynamic disks save host disk space during initial installation, their lack of automatic contraction is what leads to long-term storage bloat.

Step 1: Cleaning Linux Storage

Before starting compaction on the host, purge temporary build files and caches inside the Linux guest distribution:

# Inside the WSL2 Linux console

# Prune package manager caches
sudo apt autoremove -y
sudo apt clean
sudo apt autoclean

# Purge unused Docker objects, build builder stages, and caching targets
docker system prune -af --volumes

# Clear package runner and pip folders
npm cache clean --force
pnpm store prune
pip cache purge

# Identify large workspace directories consuming storage
du -sh ~/*/ 2>/dev/null | sort -hr | head -10

Additionally, check your home directory for old application logs or build files (such as .cache, .npm, or .parcel-cache directories) that are no longer in use. Clearing these files prior to running the compaction process maximizes the recovered storage.

Step 2: Manual Compaction using Diskpart

The diskpart utility is built into all Windows versions, including Windows Home. To perform compaction, you must run it from an elevated Windows command line or PowerShell terminal (Run as Administrator) after shutting down the WSL VM.

# In Windows PowerShell (Run as Administrator)

# 1. Terminate all active WSL processes
wsl --shutdown

# Wait for process handles to release
Start-Sleep -Seconds 5

# 2. Locate the target ext4.vhdx file path
$vhdxPath = (Get-ChildItem -Path "$env:LOCALAPPDATA\Packages" -Recurse -Filter "ext4.vhdx" | Select-Object -First 1).FullName
Write-Host "Located VHDX Target: $vhdxPath"

# 3. Create a temporary script for Diskpart execution
$diskScript = @"
select vdisk file="$vhdxPath"
attach vdisk readonly
compact vdisk
detach vdisk
exit
"@

$tempScriptPath = "$env:TEMP\compact_cmd.txt"
$diskScript | Out-File -FilePath $tempScriptPath -Encoding ASCII

# 4. Invoke diskpart
diskpart /s $tempScriptPath
Remove-Item $tempScriptPath

The script runs Diskpart headless. The attach vdisk readonly command is necessary because Diskpart requires the virtual disk to be attached in read-only mode to perform the compaction routine safely.

Step 3: Fast Compaction using Optimize-VHD

On Windows Pro, Enterprise, and Education editions, you can use the Hyper-V Optimize-VHD PowerShell cmdlet. This method is faster because it does not require writing manual mounting scripts:

# In Windows PowerShell (Run as Administrator - Requires Hyper-V)
wsl --shutdown
Start-Sleep -Seconds 5

# Locate and compact all local VHDX targets
Get-ChildItem -Path "$env:LOCALAPPDATA\Packages" -Recurse -Filter "ext4.vhdx" | ForEach-Object {
    Write-Host "Compacting: $($_.FullName)"
    Optimize-VHD -Path $_.FullName -Mode Full
}

Step 4: Backing Up and Re-importing WSL Distributions

If compaction does not recover enough space, or if you suspect virtual disk fragmentation, you can perform a full backup, format, and re-import of your distribution:

# Windows PowerShell (Run as Administrator)

# 1. Export the active distribution to a tarball archive
wsl --export Ubuntu D:\Backup\ubuntu-backup.tar

# 2. Unregister the distribution (this deletes the ext4.vhdx file entirely)
wsl --unregister Ubuntu

# 3. Import the distribution back, creating a fresh, contiguous VHDX file
wsl --import Ubuntu C:\WSL\Ubuntu D:\Backup\ubuntu-backup.tar --version 2

This backup-and-restore process defragments the virtual filesystem, yielding maximum compaction results and moving the storage file to a secondary physical drive (e.g., D:\) if needed.

Complete Automated Compaction Script

Save this script as Compact-WSL2.ps1 to run compaction on demand:

# Compact-WSL2.ps1
# Requires Windows Administrator Privileges

#Requires -RunAsAdministrator

param(
    [switch]$UseOptimizeVHD = $false,
    [switch]$DryRun = $false
)

function Get-VHDXFiles {
    return Get-ChildItem -Path "$env:LOCALAPPDATA\Packages" -Recurse -Filter "ext4.vhdx" -ErrorAction SilentlyContinue
}

Write-Host "=== WSL2 Disk Recovery Tool ===" -ForegroundColor Cyan

$vhdxFiles = Get-VHDXFiles
if (-not $vhdxFiles) {
    Write-Host "No active VHDX targets found." -ForegroundColor Yellow
    exit 0
}

$startSize = ($vhdxFiles | Measure-Object -Property Length -Sum).Sum / 1GB
Write-Host "Target Count: $($vhdxFiles.Count)"
Write-Host "Initial Storage Footprint: $([math]::Round($startSize, 2)) GB"

if ($DryRun) {
    Write-Host "Preview complete (Dry Run flag active)."
    exit 0
}

Write-Host "Shutting down WSL instances..." -ForegroundColor Yellow
wsl --shutdown
Start-Sleep -Seconds 8

foreach ($vhdx in $vhdxFiles) {
    $before = $vhdx.Length / 1GB
    Write-Host "Processing: $($vhdx.Name)"
    
    try {
        if ($UseOptimizeVHD) {
            Optimize-VHD -Path $vhdx.FullName -Mode Full -ErrorAction Stop
        } else {
            $cmdText = @"
select vdisk file="$($vhdx.FullName)"
attach vdisk readonly
compact vdisk
detach vdisk
exit
"@
            $scriptFile = [System.IO.Path]::GetTempFileName() + ".txt"
            $cmdText | Out-File -FilePath $scriptFile -Encoding ASCII
            diskpart /s $scriptFile | Out-Null
            Remove-Item $scriptFile -ErrorAction SilentlyContinue
        }
        
        $vhdx.Refresh()
        $after = $vhdx.Length / 1GB
        $saved = $before - $after
        Write-Host "Result: Saved $([math]::Round($saved, 2)) GB" -ForegroundColor Green
    } catch {
        Write-Host "Error compacting drive: $($_.Exception.Message)" -ForegroundColor Red
    }
}

Scheduled Task Automation

Configure this compaction script to run weekly using Windows Task Scheduler:

# Register weekly task execution for Sunday morning
$taskAction = New-ScheduledTaskAction `
    -Execute "PowerShell.exe" `
    -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$env:USERPROFILE\scripts\Compact-WSL2.ps1`""

$taskTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At "04:00"

$taskSettings = New-ScheduledTaskSettingsSet -RunOnlyIfIdle -IdleDuration (New-TimeSpan -Minutes 10)

$taskPrincipal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -RunLevel Highest

Register-ScheduledTask `
    -TaskName "WSL2-VHDX-Compaction" `
    -Action $taskAction `
    -Trigger $taskTrigger `
    -Settings $taskSettings `
    -Principal $taskPrincipal `
    -Force

What Breaks in Production: Failure Modes and Mitigations

Performing disk optimizations introduces system lock risks.

1. VHDX File Lock and Process Contention Errors

If another service or application holds active handles on the ext4.vhdx file, the compaction script will fail.

  • Failure Mode: Diskpart displays an error stating the file is in use. This occurs if Docker Desktop is still running, or if VS Code has not fully terminated its background terminal instances.
  • Mitigation: Ensure that you shut down the Docker Desktop service and wait for the wsl --shutdown command to complete before starting compaction.

2. Disk Read-Write Interruption and VHDX Corruption

If the host machine loses power or is forced to restart during compaction, the VHDX file can become corrupted.

  • Failure Mode: The VHDX metadata sector is damaged, preventing WSL2 from booting and throwing a Mounting virtual disk failed error.
  • Mitigation: Always perform a backup of the target ext4.vhdx file before executing manual partition operations in Diskpart.

3. Windows PowerShell Execution Policy Restrictions

Windows restricts the execution of unsigned scripts by default.

  • Failure Mode: Attempting to run Compact-WSL2.ps1 throws an execution policy error, blocking the task.
  • Mitigation: Bypass the restriction when launching the script by running the PowerShell console with -ExecutionPolicy Bypass:
    powershell -ExecutionPolicy Bypass -File .\Compact-WSL2.ps1
    

4. Excessive Wear on Solid-State Drives (SSDs)

Running deep disk compaction weekly forces the OS to rearrange blocks on disk.

  • Failure Mode: Running compaction too frequently increases write wear on solid-state drives, reducing their lifespan.
  • Mitigation: Run compaction monthly rather than daily. Compacting only when the dynamic file exceeds its actual storage size by 20GB or more is a good rule of thumb.

Frequently Asked Questions

What is the exact difference between Diskpart compact vdisk and Optimize-VHD?

Diskpart is a legacy disk utility available on all Windows editions. It requires writing command script files to attach and compact the drive. Optimize-VHD is a modern Hyper-V PowerShell cmdlet that performs compaction directly without manual attachment.

How do I bypass the PowerShell execution policy restriction to run this script?

Execute the script from the command line while passing the bypass parameter:

powershell -ExecutionPolicy Bypass -File .\Compact-WSL2.ps1

Can I run compaction while my WSL2 distribution is active?

No. Compacting a mounted VHDX file can corrupt file systems and databases. Always run wsl --shutdown and close all WSL windows before compacting.

How do I check if my VHDX file is using the sparse format?

Run this command in Windows PowerShell:

Get-Item -Path $env:LOCALAPPDATA\Packages\CanonicalGroupLimited.*\LocalState\ext4.vhdx | Select-Object Name, Attributes

If sparse formatting is active, the attributes list will include the SparseFile flag.

How do I relocate my WSL2 virtual disk to a different drive?

Export the distribution to a tar file: wsl --export Ubuntu D:\ubuntu.tar. Then unregister the distribution: wsl --unregister Ubuntu. Finally, import the distribution back, specifying the new path: wsl --import Ubuntu D:\WSL\Ubuntu D:\ubuntu.tar.

Wrapping Up

WSL2 disk bloat is an expected behavior of its dynamic virtual disk architecture. By purging caches inside the Linux guest and compacting the ext4.vhdx file on the Windows host, developers can reclaim gigabytes of disk space. Implementing a monthly scheduled task automates this maintenance.

Frequently Asked Questions

Why doesn't WSL2 automatically return freed storage to the host OS?

The virtual ext4 filesystem in WSL2 dynamic disks expands automatically on write operations, but Linux file systems do not trigger host compaction routines during deletions. When you delete files inside WSL2, the ext4 filesystem marks those blocks as free, but the VHDX file on Windows retains its allocated size until explicitly compacted. Windows only knows the VHDX file size, not the ext4 free space inside it.

Can dynamic compaction damage my codebase or databases?

Provided you run `wsl --shutdown` before starting compaction, all file handles are closed and databases are in a clean state—eliminating the risk of index corruption. Never compact a running VHDX. The Diskpart `compact vdisk` command only removes unused space identified by the sparse disk metadata; it does not modify file contents.

How much disk space can I typically recover from a WSL2 VHDX?

Recovery varies significantly based on your workflow. Developers who frequently install and uninstall large packages (node_modules, Docker images, conda environments) often recover 10–40GB. Running `sudo apt autoremove && sudo apt clean` and removing unused Docker images inside WSL2 before compacting maximizes recovery.

Does enabling sparseVhd in .wslconfig prevent VHDX from bloating?

Partially. Setting `sparseVhd=true` in .wslconfig makes the VHDX use a sparse file format, which allows Windows to see and report free space within the VHDX more accurately. However, it does not automatically reclaim this space—you still need to compact periodically. It does prevent the VHDX from appearing to consume space that Linux has already freed.