Created
August 26, 2026 11:44
-
-
Save erm3nda/1fd6b8b5b7c672d56564c65b8234fbef to your computer and use it in GitHub Desktop.
Reduce vhd docker disk size
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # reduce-docker-vhd.ps1 | |
| # Script to locate and shrink/compact the Docker Desktop WSL2 virtual disk (docker_data.vhdx) | |
| $ErrorActionPreference = "Stop" | |
| # 1. Locate the VHDX file | |
| $vhdxPath = "$env:LOCALAPPDATA\Docker\wsl\disk\docker_data.vhdx" | |
| if (-not (Test-Path $vhdxPath)) { | |
| # Search for any docker_data.vhdx or ext4.vhdx in the AppData\Local\Docker folder as a fallback | |
| $fallbackSearch = Get-ChildItem -Path "$env:LOCALAPPDATA\Docker" -Include "docker_data.vhdx", "ext4.vhdx" -Recurse -ErrorAction SilentlyContinue | Sort-Object Length -Descending | Select-Object -First 1 | |
| if ($fallbackSearch) { | |
| $vhdxPath = $fallbackSearch.FullName | |
| Write-Host "[+] Found VHDX at fallback location: $vhdxPath" -ForegroundColor Yellow | |
| } else { | |
| Write-Host "[-] Could not locate any docker_data.vhdx or ext4.vhdx file. Please ensure Docker Desktop is installed." -ForegroundColor Red | |
| Exit | |
| } | |
| } | |
| # 2. Get initial size | |
| $initialFile = Get-Item $vhdxPath | |
| $initialSizeGB = [math]::Round($initialFile.Length / 1GB, 2) | |
| Write-Host "[*] Located Docker VHDX: $vhdxPath" -ForegroundColor Cyan | |
| Write-Host "[*] Current size on disk: $initialSizeGB GB ($($initialFile.Length) bytes)" -ForegroundColor Cyan | |
| # 3. Prompt user to close Docker Desktop | |
| Write-Host "`n[!] IMPORTANT: Docker Desktop must be fully closed/exited before compacting." -ForegroundColor Yellow | |
| Write-Host "[*] Please close Docker Desktop from your system tray now." -ForegroundColor Yellow | |
| Read-Host "Press ENTER once Docker Desktop is closed to proceed with shutting down WSL" | |
| # 4. Shut down WSL | |
| Write-Host "`n[*] Stopping WSL instances..." -ForegroundColor Cyan | |
| wsl --shutdown | |
| Start-Sleep -Seconds 3 | |
| # Double-check if the file is still locked | |
| try { | |
| $fileStream = [System.IO.File]::Open($vhdxPath, 'Open', 'ReadWrite', 'None') | |
| $fileStream.Close() | |
| Write-Host "[+] VHDX file is unlocked and ready for compaction." -ForegroundColor Green | |
| } catch { | |
| Write-Host "[-] The VHDX file is still locked by another process (WSL or Docker might still be closing)." -ForegroundColor Red | |
| Write-Host "[-] Error details: $_" -ForegroundColor Red | |
| Write-Host "[*] Retrying WSL shutdown..." -ForegroundColor Yellow | |
| wsl --shutdown | |
| Start-Sleep -Seconds 5 | |
| try { | |
| $fileStream = [System.IO.File]::Open($vhdxPath, 'Open', 'ReadWrite', 'None') | |
| $fileStream.Close() | |
| Write-Host "[+] File unlocked successfully." -ForegroundColor Green | |
| } catch { | |
| Write-Host "[-] File is still locked. Please close all WSL terminals and Docker services, then run this script again." -ForegroundColor Red | |
| Exit | |
| } | |
| } | |
| # 5. Create diskpart script and run it | |
| Write-Host "`n[*] Compacting VHDX using diskpart..." -ForegroundColor Cyan | |
| $tempDiskpartScript = Join-Path $env:TEMP "compact_docker_vhd.txt" | |
| $diskpartCommands = @( | |
| "select vdisk file=`"$vhdxPath`"", | |
| "compact vdisk", | |
| "exit" | |
| ) | |
| $diskpartCommands | Out-File -FilePath $tempDiskpartScript -Encoding ascii | |
| # Run diskpart (requires admin privileges to select/compact vdisk) | |
| $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| if ($isAdmin) { | |
| diskpart /s $tempDiskpartScript | |
| } else { | |
| Write-Host "[!] Script is not running as Administrator. Requesting elevation to run diskpart..." -ForegroundColor Yellow | |
| Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command `"diskpart /s '$tempDiskpartScript'; Read-Host 'Press Enter to finish elevation'`"" -Verb RunAs -Wait | |
| } | |
| # Clean up temp file | |
| if (Test-Path $tempDiskpartScript) { | |
| # Using .NET to delete the temp file directly and avoid command shell restrictions | |
| [System.IO.File]::Delete($tempDiskpartScript) | |
| } | |
| # 6. Verify new size | |
| $finalFile = Get-Item $vhdxPath | |
| $finalSizeGB = [math]::Round($finalFile.Length / 1GB, 2) | |
| $savedSizeGB = [math]::Round($initialSizeGB - $finalSizeGB, 2) | |
| # Prepare log entry | |
| $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
| $logMessage = "[$timestamp] Original: ${initialSizeGB}GB | New: ${finalSizeGB}GB | Saved: ${savedSizeGB}GB | Path: $vhdxPath" | |
| $logPath = Join-Path $PSScriptRoot "reduce-docker-vhd.log" | |
| try { | |
| $logMessage | Out-File -FilePath $logPath -Append -Encoding utf8 | |
| } catch { | |
| Write-Host "[-] Could not write to log file: $_" -ForegroundColor Yellow | |
| } | |
| Write-Host "`n========================================" -ForegroundColor Green | |
| Write-Host "[+] Compaction completed!" -ForegroundColor Green | |
| Write-Host "[*] Original size: $initialSizeGB GB" -ForegroundColor Cyan | |
| Write-Host "[*] New size: $finalSizeGB GB" -ForegroundColor Cyan | |
| if ($savedSizeGB -gt 0) { | |
| Write-Host "[+] Space saved: $savedSizeGB GB" -ForegroundColor Green | |
| } else { | |
| Write-Host "[*] No space saved. The virtual disk was already fully compacted." -ForegroundColor Yellow | |
| } | |
| Write-Host "========================================" -ForegroundColor Green | |
| Write-Host "[+] Log saved to: $logPath" -ForegroundColor Gray | |
| Write-Host "`n[*] You can now restart Docker Desktop." -ForegroundColor Cyan | |
| Write-Host "[*] Closing window in 5 seconds..." -ForegroundColor Yellow | |
| Start-Sleep -Seconds 5 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment