Як стиснути образ Docker на Windows і повернути місце на диску
Знайома ситуація: на диску C: закінчується місце, ви видаляєте старі образи й контейнери Docker, а вільного місця не додається. Причина в тому, як Docker Desktop зберігає дані на Windows: усе лежить у віртуальному диску WSL2 у форматі .vhdx. Цей файл уміє рости, але сам ніколи не зменшується. Можна видалити з Docker 50 ГБ образів, а файл на диску займатиме стільки ж, скільки займав.
У цій статті розберемо, як знайти цей файл, почистити Docker зсередини, стиснути віртуальний диск і налаштувати систему так, щоб проблема не поверталася.
Звідки береться проблема
Docker Desktop на Windows працює всередині легкої віртуальної машини WSL2. Образи, контейнери, томи й кеш збірки зберігаються у файловій системі ext4 цієї машини, а вона, своєю чергою, лежить в одному файлі .vhdx на вашому диску.
Коли Docker записує дані, файл .vhdx збільшується. Коли дані видаляються, ext4 позначає блоки як вільні всередині диска, але Windows про це не знає, і файл лишається того самого розміру. Щоб повернути місце, віртуальний диск треба стиснути вручну.
Крок 1: знайти файл віртуального диска
Розташування залежить від версії Docker Desktop:
# Нові версії Docker Desktop (4.30 і новіші)
%LOCALAPPDATA%\Docker\wsl\disk\docker_data.vhdx
# Старіші версії
%LOCALAPPDATA%\Docker\wsl\data\ext4.vhdx
# Дуже старі версії з бекендом Hyper-V замість WSL2
C:\ProgramData\DockerDesktop\vm-data\DockerDesktop.vhdx
Якщо ви переносили диск в інше місце, точний шлях видно в Docker Desktop: Settings → Resources → Advanced → Disk image location.
Швидко подивитися розмір усіх файлів .vhdx Docker можна в PowerShell:
Get-ChildItem "$env:LOCALAPPDATA\Docker" -Recurse -Filter *.vhdx |
Select-Object FullName, @{n='SizeGB'; e={[math]::Round($_.Length / 1GB, 1)}}
Запишіть розмір: після стискання буде з чим порівняти.
Крок 2: почистити Docker зсередини
Стиснення поверне тільки те місце, яке всередині диска вже вільне. Тому спершу прибираємо зайве в самому Docker. Подивимось, що займає місце:
docker system df
Команда покаже чотири категорії: образи, контейнери, локальні томи та кеш збірки (Build Cache), а також скільки з цього можна звільнити (стовпець RECLAIMABLE). Часто найбільше місця займає саме кеш збірки, про який усі забувають.
Прибираємо поступово, від безпечного до агресивного:
# Зупинені контейнери, невикористовувані мережі, "висячі" образи
docker system prune
# Кеш збірки (часто це десятки гігабайт)
docker builder prune -a
# Усі образи, які не використовує жоден контейнер
docker image prune -a
# Невикористовувані томи (УВАГА: тут можуть бути дані баз!)
docker volume prune
Або все одразу однією командою:
docker system prune -a --volumes
Обережно з томами. Ключ --volumes і команда docker volume prune видаляють томи, не підключені до жодного контейнера. Якщо ви зупинили проєкт через docker compose down, його том з базою даних теж вважається невикористовуваним. Перед видаленням перегляньте список: docker volume ls.
Крок 3: звільнити блоки всередині диска (TRIM)
Цей крок не обов'язковий, але робить стиснення значно ефективнішим. Команда fstrim повідомляє віртуальному диску, які блоки файлової системи справді вільні. Без неї стиснення іноді звільняє лише частину місця.
Docker Desktop не дає прямого доступу до своєї віртуальної машини, але туди можна потрапити через привілейований контейнер:
docker run --rm --privileged --pid=host alpine nsenter -t 1 -m -- fstrim -av
Команда виведе, скільки місця позначено як вільне. Контейнер видалиться одразу після виконання.
Крок 4: зупинити Docker і WSL
Файл .vhdx не можна стискати, поки він використовується. Тому:
- Закрийте Docker Desktop: правою кнопкою по іконці кита в треї → Quit Docker Desktop.
- Зупиніть усі дистрибутиви WSL:
wsl --shutdown
# Перевірити, що нічого не запущено (усі мають статус Stopped)
wsl -l -v
Крок 5: стиснути віртуальний диск
Є два способи. Обидва потребують PowerShell або командного рядка від імені адміністратора.
Спосіб 1: diskpart (працює на будь-якій Windows, включно з Home)
diskpart
У вікні diskpart по черзі виконайте команди (шлях замініть на свій з кроку 1, %LOCALAPPDATA% тут не працює, потрібен повний шлях):
select vdisk file="C:\Users\ВашеІм'я\AppData\Local\Docker\wsl\disk\docker_data.vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit
Команда compact vdisk показує прогрес у відсотках і може працювати кілька хвилин, залежно від розміру диска. Підключення в режимі readonly безпечне: дані всередині диска не змінюються.
Спосіб 2: Optimize-VHD (Windows Pro, Enterprise, Education)
Якщо в системі ввімкнено Hyper-V, є коротший шлях однією командою:
Optimize-VHD -Path "$env:LOCALAPPDATA\Docker\wsl\disk\docker_data.vhdx" -Mode Full
Якщо PowerShell відповідає, що команду Optimize-VHD не знайдено, значить модуль Hyper-V не встановлено. Користуйтеся способом з diskpart, результат той самий.
Після стискання запустіть Docker Desktop і переконайтеся, що образи й контейнери на місці. Потім ще раз перевірте розмір файлу командою з кроку 1. На практиці диск, що виріс до 60–80 ГБ, після чистки й стискання часто зменшується в кілька разів.
Скрипт, який робить усе сам
Щоб не повторювати кроки вручну, збережіть скрипт як compact-docker.ps1:
# compact-docker.ps1 — стискає віртуальний диск Docker Desktop
# Запуск: PowerShell від імені адміністратора
$disk = "$env:LOCALAPPDATA\Docker\wsl\disk\docker_data.vhdx"
if (-not (Test-Path $disk)) { $disk = "$env:LOCALAPPDATA\Docker\wsl\data\ext4.vhdx" }
if (-not (Test-Path $disk)) { Write-Error "Диск Docker не знайдено"; exit 1 }
$before = (Get-Item $disk).Length / 1GB
Write-Host "Зупиняю Docker Desktop і WSL..."
Get-Process "Docker Desktop" -ErrorAction SilentlyContinue | Stop-Process
Start-Sleep -Seconds 10
wsl --shutdown
Write-Host "Стискаю $disk ..."
$script = "$env:TEMP\compact-docker.txt"
@"
select vdisk file="$disk"
attach vdisk readonly
compact vdisk
detach vdisk
"@ | Set-Content -Path $script -Encoding ascii
diskpart /s $script
Remove-Item $script
$after = (Get-Item $disk).Length / 1GB
Write-Host ("Готово: {0:N1} ГБ -> {1:N1} ГБ" -f $before, $after)
Запускається так (з PowerShell від адміністратора, з папки, де лежить скрипт):
powershell -ExecutionPolicy Bypass -File .\compact-docker.ps1
Ключ -ExecutionPolicy Bypass потрібен, бо за замовчуванням Windows забороняє запускати незнайомі скрипти. Він діє лише для цього одного запуску й не змінює налаштувань системи. Перед запуском скрипта не забудьте почистити Docker (крок 2), інакше стискати буде нічого.
Як не повертатися до проблеми
Автоматичне зменшення диска (sparse VHD)
Нові версії WSL вміють працювати з «розрідженими» (sparse) дисками, які віддають звільнене місце системі автоматично. Увімкнути для нових дисків можна у файлі %USERPROFILE%\.wslconfig:
[experimental]
sparseVhd=true
Для вже наявного дистрибутива режим вмикається командою (потрібна свіжа версія WSL, оновлення: wsl --update):
wsl --shutdown
wsl --manage docker-desktop --set-sparse true
Налаштування поки має статус експериментального, тож якщо після нього щось працює нестабільно, просто поверніть ручне стискання раз на кілька місяців.
Перенести диск з C: на інший розділ
Якщо на C: місця мало, найпростіше рішення — перенести диск Docker на інший розділ. У Docker Desktop: Settings → Resources → Advanced → Disk image location, вибрати нову папку й натиснути Apply & restart. Docker перенесе файл сам, усі образи й контейнери збережуться.
У тому ж розділі є повзунок Virtual disk limit, який задає максимальний розмір диска. Він не дасть Docker непомітно з'їсти весь вільний простір.
Регулярне прибирання
Найбільше місця накопичує кеш збірки. Якщо ви часто збираєте образи, виконуйте docker builder prune хоча б раз на тиждень-два. Перевірити стан можна тією ж командою docker system df.
Те саме для інших дистрибутивів WSL
Так само росте диск будь-якого дистрибутива WSL, наприклад Ubuntu. Його файл лежить у папці пакета:
%LOCALAPPDATA%\Packages\CanonicalGroupLimited.Ubuntu*\LocalState\ext4.vhdx
Порядок дій той самий: всередині Ubuntu видалити зайве й виконати sudo fstrim -av, потім wsl --shutdown і стиснути файл через diskpart або Optimize-VHD.
Як зменшити самі образи
Стискання диска повертає місце, але краще, щоб образи взагалі займали менше. Кілька прийомів, які дають найбільший ефект:
- Легкі базові образи.
node:20-alpineважить у кілька разів менше заnode:20, аpython:3.12-slim— менше заpython:3.12. - Багатоетапна збірка. Компілюємо й збираємо в одному образі, а у фінальний копіюємо лише результат. Без інструментів збірки й
node_modulesобраз худне в рази. - Чистка кешу пакетного менеджера в тому ж шарі. Кожна інструкція
RUN— окремий шар, і видалення файлів у наступному шарі не зменшує попередній:
# Погано: кеш apt лишається в шарі
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# Добре: усе в одному шарі
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
.dockerignore. Не дає потрапити в образ папкам.git,node_modules, логам і тестам.- Аналіз образу. Подивитися, який шар скільки важить:
docker history назва-образу. Для детального аналізу є утиліта dive, яка показує вміст кожного шару й підказує, де зайві файли.
Детальніше про Dockerfile, багатоетапну збірку та .dockerignore — у статті Docker для веброзробника: від першого запуску до продакшну.
Висновок
Docker на Windows не повертає місце сам, бо всі дані лежать у віртуальному диску .vhdx, який тільки росте. Рецепт короткий: почистити Docker через docker system prune і docker builder prune, за бажанням виконати fstrim, зупинити Docker і WSL та стиснути файл через diskpart або Optimize-VHD. А щоб не робити цього постійно, увімкніть sparse VHD, обмежте розмір диска в налаштуваннях і не забувайте про кеш збірки.