Prune и disk usage: когда диск забит build-кэшем
Самая частая проблема DE-разработчика после нескольких месяцев работы с Docker: no space left on device при build или pull. Виноваты обычно три категории мусора: dangling образы (без тегов), остановленные контейнеры с их writable layers, и — самое толстое — build cache от BuildKit. На активной dev-машине это может занимать 50-100GB.
В этом уроке: как диагностировать что забило диск, как чистить, и как настроить автоматическую очистку на CI.
Disk emergency: что делать когда диск 100%
docker system df: что занимает место
Первая команда при «диск забит»:
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 47 12 8.234GB 5.123GB (62%)
Containers 15 2 234MB 189MB (80%)
Local Volumes 23 5 12.45GB 8.234GB (66%)
Build Cache 128 0 15.67GB 15.67GB (100%)
Колонки:
- TYPE — категория артефактов.
- TOTAL — общее количество объектов этого типа.
- ACTIVE — сколько используется прямо сейчас (есть запущенный контейнер / volume замаунтен / образ имеет тег и используется).
- SIZE — суммарный размер.
- RECLAIMABLE — сколько можно высвободить через prune (то, что не active).
В примере: 5.1GB можно убрать из образов, 189MB из контейнеров, 8GB из volumes, 15.7GB из build cache. Итого ~29GB которые можно высвободить за две команды.
Подробнее по объектам:
$ docker system df --verbose
Images space usage:
REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNIQUE SIZE
python 3.13 abc... 2 weeks ago 145MB 0B 145MB
etl v1 def... 1 hour ago 812MB 145MB 667MB
<none> <none> 123abc 5 hours ago 400MB 0B 400MB # dangling!
...
Containers space usage:
...
Local Volumes space usage:
VOLUME NAME LINKS SIZE
postgres_data 1 3.2GB
abandoned_volume_xyz 0 4.8GB # никто не использует
Записи <none>:<none> это dangling images — образы, потерявшие тег (обычно от пересборки: docker build -t app:v1 . второй раз — старый image теряет тег, остаётся без имени). Volume с LINKS=0 — никем не используется.
docker image prune: удаление образов
Два режима:
# Удалить только dangling (без тегов)
docker image prune
# WARNING! This will remove all dangling images.
# Are you sure you want to continue? [y/N] y
# Deleted Images:
# untagged: <none>@sha256:abc...
# deleted: sha256:def...
# Total reclaimed space: 1.234GB
# Удалить ВСЕ не-используемые образы (без -a — только dangling)
docker image prune -a
# WARNING! This will remove all images without at least one container associated to them.
# ...
# Total reclaimed space: 8.234GB
-a агрессивно: удаляет все образы, на которые сейчас нет ссылающихся контейнеров. Если ты pull’ишь много разных образов для тестов и не запускаешь их 24/7 — будут удалены. С --filter "until=24h" можно ограничить «старше 24 часов».
# Удалить образы старше 7 дней без активных контейнеров
docker image prune -a --filter "until=168h"
docker container prune: остановленные контейнеры
Остановленные контейнеры (exited) занимают место своими writable layers:
$ docker ps -a -f "status=exited"
CONTAINER ID IMAGE COMMAND CREATED STATUS
abc123 alpine sleep 60 2 days ago Exited (137) 1 day ago
def456 python:3.13 python 1 week ago Exited (0) 1 week ago
# Удалить все
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
abc123...
def456...
Total reclaimed space: 234MB
В CI и в продакшене обычно используют docker run --rm чтобы контейнеры самоудалялись после exit. Без --rm они оставляют writable layers.
docker volume prune: anonymous volumes
Anonymous volumes создаются автоматически в нескольких случаях:
- В образе есть
VOLUME /data— Docker создаёт anonymous volume приdocker run - В compose не указан volume для named-volume — иногда создаётся anonymous
- Manual
docker run -v /data(без имени)
Эти volumes часто никем не используются и забывается, но занимают диск.
$ docker volume ls
DRIVER VOLUME NAME
local postgres_data
local a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab # anonymous
local 0a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab # anonymous
$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
# Будут удалены только volumes БЕЗ ссылающихся контейнеров (включая остановленные)
# Удалить все anonymous + named (более агрессивно)
docker volume prune -a
docker volume prune удаляет данные БЕЗВОЗВРАТНО. Если в volume лежит база Postgres с production-данными dev-стенда — потеряешь её. Перед prune всегда проверяй docker volume ls и docker volume inspect. Для named volumes лучше удалять руками по имени: docker volume rm postgres_data.
docker builder prune: главный жирный кусок
BuildKit (default builder в Docker 23+) держит кэш сборок отдельно от images. Этот кэш растёт от каждого docker build, особенно если в Dockerfile используется RUN --mount=type=cache:
$ docker builder prune
WARNING! This will remove all dangling build cache.
Are you sure you want to continue? [y/N] y
Total: 15.67GB
# Удалить ВЕСЬ build cache (включая используемый)
docker builder prune -a
# С фильтром
docker builder prune --filter "until=72h" # старше 3 дней
docker builder prune --filter "type=regular,description=*pip*"
Если ты собираешь много образов, build cache это то, что нужно мониторить. На active dev-машине без очистки может занимать 50GB+ за месяц.
docker system prune: всё разом
docker system prune чистит образы + контейнеры + сети + build cache в одной команде:
# Безопасный режим: только dangling + stopped containers
docker system prune
# Агрессивный: + все не-используемые образы
docker system prune -a
# + volumes (НЕ default, потому что опасно)
docker system prune -a --volumes
# С фильтром по дате
docker system prune -a --filter "until=72h"
Это удобная «one-liner» команда для еженедельной чистки dev-машины.
Cron-cleanup на CI
В CI каждый build добавляет образы и cache. Без очистки runner забивается за дни.
Подходы:
1. После каждого workflow: docker system prune -a --volumes --filter "until=4h" -f. Уберёт всё старше 4 часов кроме того, что используется прямо сейчас. -f (force) убирает confirm-prompt.
# .github/workflows/build.yml
jobs:
build:
steps:
- run: docker build -t app:latest .
# ... тесты ...
- if: always()
run: docker system prune -a --volumes --filter "until=4h" -f
2. Standalone cleanup job, scheduled:
# .github/workflows/cleanup.yml
on:
schedule:
- cron: '0 4 * * *' # ежедневно в 4 утра
jobs:
cleanup:
runs-on: self-hosted
steps:
- run: docker system prune -a --volumes -f
- run: docker builder prune -a -f
3. На self-hosted runner: в systemd-timer:
# /etc/systemd/system/docker-cleanup.timer
[Timer]
OnCalendar=daily
Persistent=true
# /etc/systemd/system/docker-cleanup.service
[Service]
ExecStart=/usr/bin/docker system prune -a --volumes --filter "until=24h" -f
Восстановление когда диск 100%
Если уже no space left:
# 1. Освободить место чем угодно за пределами Docker
sudo apt clean # на хосте
rm -rf ~/.cache/pip
# 2. Если хватает места для docker daemon -- идём
docker system prune -a --volumes -f
# 3. Если daemon упал -- останавливаем и чистим вручную
sudo systemctl stop docker
# Удалить build cache напрямую (на Linux)
# ОСТОРОЖНО: уничтожит ВСЕ build-кэши, не только устаревшие
# sudo rm -rf /var/lib/docker/buildkit/
# Удалить overlay2 dangling layers (требует knowledge о слоях)
# sudo rm -rf /var/lib/docker/overlay2/<id-of-dangling>/
# 4. Запустить обратно
sudo systemctl start docker
# 5. Проверить
docker system df
На macOS с Docker Desktop / OrbStack: меньше опций, потому что данные в VM. Reset to factory defaults — ядерный вариант: удалит ВСЁ (все образы, контейнеры, volumes). После нужно pull всех образов заново.
Безопасный prune для прода
На production хостах с долго-живущими контейнерами:
# Только остановленные контейнеры (running не трогаются)
docker container prune -f
# Только dangling images (без тегов)
docker image prune -f
# Build cache (если builds происходят на этом хосте)
docker builder prune --filter "until=168h" -f # старше недели
# НЕ ИСПОЛЬЗОВАТЬ на проде:
# docker volume prune -- может удалить prod-данные
# docker system prune -a --volumes -- слишком агрессивно
В Kubernetes хостах есть kubelet image garbage collection — настраивается через --image-gc-high-threshold / --image-gc-low-threshold. Эта система чистит сама, ручные prune не нужны.
Попробуй сам
Проверь, сколько у тебя сейчас Docker занимает:
docker system df
# Если интересно -- подробности
docker system df --verbose
# Уберём dangling и stopped (безопасно)
docker container prune -f
docker image prune -f
# Если хочешь почистить build cache (после убедиться, что cache не нужен прямо сейчас)
docker builder prune -f
# Проверим освобождённое
docker system df
# Опционально: глубокая очистка (НЕ на машине, где есть важные volumes!)
# docker system prune -a --volumes -f
Заведи привычку раз в неделю на dev-машине запускать docker system prune -f (без -a, чтобы не потерять полезные образы). Build cache отдельно — docker builder prune --filter "until=72h" -f.