MAATRIX / Блог / Ритуал чистки Docker: образы, тома и сети, которые копятся годами

Ритуал чистки Docker: образы, тома и сети, которые копятся годами

MAATRIX

Диск на сервере с Docker никогда не заканчивается резко — он утекает по каплям: остановленный контейнер после неудачного деплоя, tagged-образ прошлой версии, том, который забыли отмонтировать, сеть от давно снесённого compose-проекта. По отдельности каждый объект занимает мегабайты, но через год-два таких объектов накапливаются тысячи, и df -h внезапно показывает 95% занятого места на сервере, где, по ощущениям, ничего особенного не происходило. Ниже — рабочий ритуал: как увидеть, что реально копится, какие команды чистят это безопасно, а какие способны стереть данные, которые выглядели «неиспользуемыми», но таковыми не были.

Откуда берётся мусор в Docker и почему он копится годами

Docker ничего не удаляет сам — управление жизненным циклом объектов полностью отдано на откуп тому, кто им пользуется, а на практике этим занимаются только когда место уже кончилось. Источников накопления обычно четыре.

Образы. При каждом docker build, если новый образ получает тот же тег, что и предыдущий, старый образ не удаляется — он теряет тег и остаётся в системе как <none>:<none> (dangling image), занимая место всеми своими слоями. При деплое несколько раз в день таких «висячих» образов набирается по несколько штук ежедневно.

Контейнеры. Без флага --rm остановленный контейнер никуда не девается — вместе с ним остаются его логи и записываемый слой (writable layer). Неудачные деплои, разовые docker run для отладки, CI-раннеры, которые не подчищают за собой — всё это оставляет «трупы» контейнеров.

Тома. Анонимные тома, созданные для сервисов без явного указания volume в compose-файле, переживают контейнер и остаются с именем-хэшем. Именованные тома от проектов, которые снесли через docker compose down (без -v — это осознанная защита от случайной потери данных), тоже никуда не деваются.

Сети. Docker Compose создаёт отдельную bridge-сеть под каждый проект. Если проект тестировали под разными именами или он был снесён не полностью, осиротевшие сети копятся тихо и незаметно.

Отдельная и часто недооценённая статья — build-кеш BuildKit: на хосте с активной CI/CD-сборкой он способен вырасти до десятков гигабайт, никак не отражаясь в выводе docker images. Если счёт за диск на сервере уже вырос сам по себе, стоит сначала посмотреть разбор причин, почему Docker съедает всё место на диске — там разобраны частные случаи вроде логов и orphaned-слоёв, которые prune не решает.

Как увидеть реальную картину, прежде чем что-то удалять

Первое правило ритуала — не чистить вслепую. Быстрая сводка по всем категориям:

docker system df

Вывод выглядит примерно так (цифры ниже условны, у вас будут свои):

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          142       18        38.4GB    31.2GB (81%)
Containers      27        4         420MB     380MB (90%)
Local Volumes   56        12        14.7GB    9.1GB (62%)
Build Cache     284       0         6.8GB     6.8GB (100%)

Колонка RECLAIMABLE — это то, что теоретически можно освободить, но не то, что нужно освобождать не глядя: в неё попадают и безопасные dangling-образы, и тома с данными, которые просто сейчас не примонтированы ни к одному контейнеру. Для детализации по каждому объекту:

docker system df -v

Это покажет по каждому образу, тому и контейнеру — его размер и то, используется ли он сейчас. Отдельно полезны прицельные списки:

docker images -a
docker ps -a
docker volume ls
docker network ls
docker images -f dangling=true
docker volume ls -f dangling=true

Последние две команды с фильтром dangling=true — это именно то, что в первую очередь является кандидатом на удаление. Не пытайтесь оценивать занятое место через du напрямую по /var/lib/docker — на overlay2-бэкенде слои переиспользуются между образами нелинейно, и сумма размеров каталогов не равна тому, что реально освободится при удалении. Доверяйте собственной статистике Docker, а не файловой системе напрямую.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Осторожная чистка: что можно удалять почти не глядя

Часть объектов Docker убирает без риска для данных в принципе — их можно чистить регулярно и без ручной проверки.

Остановленные контейнеры, с фильтром по возрасту — чтобы контейнер, упавший пять минут назад, ещё можно было успеть посмотреть перед удалением:

docker container prune -f --filter "until=24h"

Dangling-образы — те самые <none>:<none>. Они по определению не используются ни одним контейнером и не имеют тега, поэтому команда не заденет ничего, чем вы пользуетесь:

docker image prune -f

Неиспользуемые пользовательские сети. Стандартные bridge, host и none защищены и никогда не удаляются, тронуть можно только сети без единого подключённого контейнера:

docker network prune -f

Build-кеш старше определённого возраста — при следующей сборке он просто пересоздастся заново, ничего критичного тут нет:

docker builder prune -f --filter "until=240h"

Собранный вместе, это безопасный набор, который можно запускать на любом сервере без предварительного ревью:

docker container prune -f --filter "until=24h" \
  && docker image prune -f \
  && docker network prune -f \
  && docker builder prune -f --filter "until=240h"

Ни одна из этих четырёх команд не трогает тома — и это не случайность, а ровно то место, где осторожность нужна больше всего.

Тома с данными: где чистка может стоить вам данных

docker volume prune удаляет все тома, на которые прямо сейчас не ссылается ни один контейнер — независимо от того, был ли контейнер остановлен намеренно на минуту или снесён случайно полгода назад. Ловушка в том, что docker compose down по умолчанию именованные тома не трогает — это осознанная защита, — но при «уборке» её нередко переопределяют флагом -v/--volumes, не отдавая себе отчёта, что именно в этом томе лежит база данных проекта, который просто временно не запущен.

Прежде чем удалять том, который выглядит неиспользуемым, стоит пройти три шага.

  1. Составить список кандидатов:
docker volume ls -f dangling=true
  1. Посмотреть на каждый — метки (обычно там видно, из какого compose-проекта он родом) и точку монтирования:
docker volume inspect <volume_name>
  1. Заглянуть внутрь и оценить размер, не подключая том к реальному сервису:
docker run --rm -v <volume_name>:/data alpine du -sh /data
docker run --rm -v <volume_name>:/data alpine ls -la /data

Если внутри видны файлы вроде pgdata, каталог mysql, файлы .db или что-то похожее на пользовательские данные — не удаляйте том сразу, даже если проект «вроде бы» закрыт. Сначала снимите бэкап:

docker run --rm -v <volume_name>:/data -v $(pwd):/backup alpine \
  tar czf /backup/<volume_name>-$(date +%F).tar.gz -C /data .

Регулярный процесс бэкапа томов — отдельная задача, она подробно разобрана в статье про настройку бэкапа Docker-томов. Если ещё не до конца понятно, какие у вас вообще тома — именованные, анонимные или bind mount, — это стоит выяснить в первую очередь, потому что от типа тома зависит и риск, и способ бэкапа: см. разбор типов Docker-томов.

Практика, которая избавляет от ручной проверки в будущем — маркировать метками тома, которые точно нельзя трогать автоматикой:

volumes:
  db_data:
    labels:
      keep: "true"

и исключать их из чистки фильтром:

docker volume prune -f --filter "label!=keep=true"

Это переворачивает логику с «удалить всё неиспользуемое и понадеяться» на «защитить явно помеченное, остальное — под нож». Именно docker volume prune--volumes в связке с system prune, о которой ниже) — единственная команда в этом ритуале, способная необратимо стереть данные, и относиться к ней стоит соответственно.

Агрессивная чистка: docker system prune -a --volumes и когда она оправдана

Полная команда убирает разом всё, что не используется прямо сейчас:

docker system prune -a --volumes -f

Флаг -a меняет поведение принципиально: вместо только dangling-образов удаляются все образы, не привязанные ни к одному контейнеру, включая помеченные тегом. Это значит, что предзагруженный образ для следующего деплоя, базовый образ, закешированный ради ускорения будущей сборки, или образ прошлой версии, оставленный «на всякий случай» для отката — всё это будет удалено, и следующий деплой заново скачает всё из реестра, что занимает время и трафик.

Флаг --volumes добавляет к прочему поведение docker volume prune — с тем же риском для данных, что описан выше. Без -f Docker перед выполнением покажет список того, что будет удалено, и попросит подтверждения — имеет смысл хотя бы раз прогнать команду без -f, чтобы увидеть реальный масштаб, прежде чем автоматизировать её с этим флагом.

КомандаЧто удаляетРиск потери данных
docker container pruneОстановленные контейнерыНет
docker image pruneТолько dangling-образы (<none>)Нет
docker network pruneНеиспользуемые пользовательские сетиНет
docker builder pruneBuild-кеш BuildKitНет, пересоберётся заново
docker image prune -aВсе образы без привязки к контейнеру, включая taggedДанных нет, но следующий деплой дольше
docker volume pruneТома без привязки к контейнеруДа, если том хранил данные
docker system prune -a --volumesВсё перечисленное сразуДа, максимальный

Агрессивная чистка оправдана там, где состояние хоста в принципе не должно переживать пересборку: CI/CD-раннеры и эфемерные сборочные агенты, которые и так пересоздаются периодически с нуля; одноразовые staging-окружения, поднимаемые заново из git и compose-файла, где потеря локального состояния — это ожидаемое поведение, а не инцидент; личные dev-песочницы. На боевом сервере с именованными томами, где лежат реальные данные, --volumes в паре с -a без предварительного ревью — плохая идея почти всегда.

Регулярный запуск через cron: разумные флаги и логирование

Диск заполняется медленно, поэтому для большинства серверов достаточно еженедельной чистки — гонять её каждую ночь без реальной необходимости смысла нет, это лишняя нагрузка на диск ради экономии, которая копится неделями, а не часами. Исключение — CI-хосты с интенсивной пересборкой образов, там дневная чистка build-кеша и dangling-образов оправдана.

Для обычного сервера с данными — «мягкий» профиль, безопасные команды раз в неделю ночью:

# /etc/cron.d/docker-cleanup
0 4 * * 0 root docker container prune -f --filter "until=24h" >> /var/log/docker-cleanup.log 2>&1
5 4 * * 0 root docker image prune -f >> /var/log/docker-cleanup.log 2>&1
10 4 * * 0 root docker network prune -f >> /var/log/docker-cleanup.log 2>&1
15 4 * * 0 root docker builder prune -f --filter "until=336h" >> /var/log/docker-cleanup.log 2>&1

Удобнее собрать всё в один скрипт — так проще смотреть в логе, сколько места реально освободилось за один прогон:

#!/usr/bin/env bash
LOG=/var/log/docker-cleanup.log
{
  echo "=== $(date -Iseconds) before ==="
  df -h /
  docker container prune -f --filter "until=24h"
  docker image prune -f
  docker network prune -f
  docker builder prune -f --filter "until=336h"
  echo "=== $(date -Iseconds) after ==="
  df -h /
} >> "$LOG" 2>&1

Сохраните как /usr/local/bin/docker-cleanup.sh, дайте право на выполнение, а в cron вызывайте уже сам скрипт — так историю запусков удобно листать одним файлом, а не собирать из отдельных строк crontab. Файл лога стоит завести в logrotate, иначе он сам через год-два станет тем же самым мусором, с которым вы боретесь.

Для CI-раннеров и одноразовых стендов, где допустима агрессивная чистка без разбора, — ежедневный полный прогон:

0 3 * * * root docker system prune -a --volumes -f >> /var/log/docker-cleanup.log 2>&1

Если сомневаетесь, можно ли доверить --volumes автоматике даже на таком хосте — используйте вариант с меткой-исключением вместо полного доверия:

docker volume prune -f --filter "label!=keep=true" >> /var/log/docker-cleanup.log 2>&1

Эта чистка — частный случай более общей практики: регулярно, по расписанию, а не по факту инцидента, проходиться по инфраструктуре и убирать то, что накопилось незаметно. Если такого цикла на уровне всего сервера ещё нет, имеет смысл посмотреть на ритуал годового техосмотра инфраструктуры — чистка Docker логично встраивается в него как один из регулярных пунктов, а не отдельная забота, о которой вспоминают только когда место кончилось.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Есть ли у docker system prune режим предпросмотра (dry-run)?

Встроенного флага --dry-run нет. Ближайший эквивалент — посмотреть docker system df -v и списки docker images -f dangling=true / docker volume ls -f dangling=true перед запуском, чтобы понимать заранее, что попадёт под удаление.

Затронет ли prune работающие контейнеры?

Нет. Ни одна из команд prune не трогает запущенные контейнеры и то, что они используют — ни их образы, ни их тома, ни их сети удалены не будут, даже с флагом -a.

Что будет, если попытаться удалить сеть, которая ещё используется?

Docker вернёт ошибку вида network ... has active endpoints и ничего не удалит. В этом смысле сети безопаснее томов — здесь нет риска тихой потери данных, есть только явный отказ выполнить команду.

Почему после чистки место всё равно не освободилось?

Часто дело не в образах и томах, а в логах контейнеров без ротации (драйвер json-file по умолчанию не режет файлы сам) или в orphaned-слоях, оставшихся после того, как демон был прерван во время предыдущей чистки. В таком случае помогает перезапуск dockerd и повторная сверка через docker system df -v.

Можно ли чистить build-кеш на боевом сервере так же смело, как на CI?

Да — docker builder prune не трогает ничего, кроме кеша слоёв сборки. Единственная цена — чуть более долгая следующая сборка, никакого риска для рантайма или данных тут нет.

Стоит ли останавливать все контейнеры перед общей чисткой?

Нет и не нужно — наоборот, работающие контейнеры служат естественной защитой: всё, что они используют, prune не тронет ни при каких флагах.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →