2 ноября 2026 г.

Docker забил диск: как найти, что занимает место, и безопасно очистить

Как найти, что занимает место: образы, остановленные контейнеры, тома и огромные логи. Что чистить безопасно, а что нет.

Сервер стоит на Docker, и однажды сборка падает с «no space left on device», а df -h показывает 100%. Сам Docker место не «теряет»: он копит образы, остановленные контейнеры, тома, кэш сборки и журналы, и ничего из этого не удаляет, пока вы не скажете. Ниже порядок действий: как за пару минут понять, что именно разрослось, что можно чистить без последствий, где легко потерять данные и как сделать так, чтобы диск не заполнялся снова.

Коротко. Посмотрите картину командой docker system df, а размер журналов отдельно: sudo sh -c 'du -h /var/lib/docker/containers/*/*-json.log | sort -h | tail'. Чистите по порядку: docker container prune, затем docker image prune, и только потом думайте про тома. Именованные тома обычная docker volume prune в нашем тесте (Docker 29.1.3) не тронула, но docker volume prune -a удалит все тома, к которым не привязан ни один контейнер, вместе с данными. Чтобы журналы не росли бесконечно, задайте ротацию в /etc/docker/daemon.json.

Куда на самом деле уходит место

У Docker пять источников роста. Четыре из них видно в docker system df, а пятый не видно совсем, и именно он нередко оказывается виноватым.

Что

Откуда берётся

Видно в docker system df

Чем чистить

Образы

каждая загрузка образа и каждая сборка; старая версия под тем же тегом остаётся как образ без тега

да, строка Images

docker image prune

Остановленные контейнеры

всё, что запускали без --rm и не удалили; внутри данные, записанные за время работы

да, строка Containers

docker container prune

Тома (volumes)

базы данных и загрузки, которые вы вынесли из контейнера; остаются после удаления контейнера

да, строка Local Volumes

docker volume prune

Кэш сборки

промежуточные слои docker build

да, строка Build Cache

docker builder prune

Журналы контейнеров

всё, что программа пишет в stdout и stderr

нет

truncate и ротация в daemon.json

Шаг 1. Убедитесь, что место съел именно Docker

Сначала посмотрим, сколько занято на диске в целом, и сравним с каталогами Docker. Если включено хранилище образов containerd (так было на нашем стенде), образы лежат не в /var/lib/docker, а в /var/lib/containerd, поэтому проверяем оба каталога. du -sh считает размер каталога целиком, sudo нужен, потому что внутри лежат файлы, принадлежащие root.

Как мы получили вывод. Чтобы не трогать боевые данные, мы подняли отдельный тестовый демон Docker с пустым хранилищем на VPS HIP-HOSTING с Ubuntu 24.04 и собрали в нём «захламлённый» сервер: несколько образов, две сборки под одним тегом, три остановленных контейнера с данными, два тома и контейнер, который без остановки пишет в журнал. Размеры ваши будут другими, а порядок действий тот же. Одно замечание о единицах: du -h считает кратными 1024, а docker десятичными МБ и ГБ, поэтому числа из двух команд немного расходятся. Длинные идентификаторы в выводах сокращены. Команды docker здесь и дальше написаны без sudo: считаем, что вы root или состоите в группе docker.

Занято 8,9 ГБ из 38, из них Docker держит около 2,7 ГБ: 1,3 ГБ в /var/lib/docker и 1,4 ГБ в /var/lib/containerd. В нашем случае образы оказались в containerd; при классическом хранилище они обычно лежат в /var/lib/docker/overlay2. Если оба каталога у вас небольшие, а диск полон, Docker не виноват, смотрите последний раздел статьи.

Шаг 2. Покажите, кто съел диск: docker system df

Команда docker system df раскладывает место Docker по категориям. Колонка SIZE показывает, сколько занимает категория, а RECLAIMABLE — сколько из этого можно вернуть, потому что ничем не используется.

Разбираем строки. В Images шесть образов и 1,475 ГБ; в колонке RECLAIMABLE Docker оценил 256,7 МБ как возвращаемые (как он их считает, мы не разбирали; фактически ниже image prune освободил 157,3 МБ, а image prune -a 313,8 МБ). В Containers четыре остановленных контейнера, 251,7 МБ, и все они подлежат очистке (100%). В Local Volumes два тома, вместе 335,5 МБ, оба «свободны», потому что к ним не привязан ни один контейнер. Строка Build Cache у нас пустая: образы собирались старым сборщиком, и на стенде кэш в этой строке не появился. На BuildKit у вас, скорее всего, будет ненулевое значение.

Подробнее по каждому образу, контейнеру и тому покажет флаг -v, но его вывод получается очень широким, поэтому мы его не приводим. Посмотрим на образы обычной командой. Обратите внимание на myapp:1.0 и на образ без имени размером те же 327 МБ: мы собрали образ под тегом myapp:1.0 два раза с изменённым файлом. Тег достался новой сборке, а старая осталась без имени. Такие образы называют «висячими» (dangling): они ничем не используются, но место занимают. В Docker 29 обычный docker images такой образ в списке не показал, а фильтр -f dangling=true показывает его как <untagged> (в других версиях вы можете увидеть <none>):

В колонке SIZE команды docker ps -a --size видно, сколько места держат остановленные контейнеры (формат вывода мы сократили до четырёх колонок ключом --format). Здесь три контейнера по 83,9 МБ: мы запускали их, чтобы они записали по 80 МБ данных, и они завершились, но не удалились.

Что docker system df не показывает: журналы контейнеров

Запомните эту строку из вывода выше: Containers ... 251.7MB. Это размер слоёв записи остановленных контейнеров, а журналы в него не входят. Журнал — это файл, куда Docker складывает всё, что программа печатает в stdout и stderr. По умолчанию он называется <id>-json.log и растёт без ограничений.

Найти самые большие журналы можно через du с сортировкой. Звёздочки в пути подставляют все контейнеры сразу, sort -h упорядочивает по размеру с учётом единиц (М, Г), а tail -3 оставляет три самых больших. Всё обёрнуто в sudo sh -c '...', потому что каталог закрыт для обычного пользователя, а звёздочки раскрывает оболочка: без обёртки она раскроет их от вашего имени и ничего не найдёт.

Контейнер chatty — это тестовая программа, которая в цикле печатает строку «обработан запрос». За 25 секунд она выдала журнал в 947 МБ. Реальное приложение пишет медленнее, но принцип тот же: болтливый сервис без ротации за недели набирает гигабайты, и docker system df этого не покажет. В нашем тесте 947 МБ из 1,3 ГБ каталога /var/lib/docker пришлось именно на журнал:

Как читать: containers в этом каталоге — это журналы и служебные файлы контейнеров, volumes — тома. Если у вас там сотни мегабайт или гигабайты в containers, ищите большой *-json.log.

Чистим по порядку: от безопасного к опасному

Идём от того, что почти не рискованно, к тому, где можно потерять данные. После каждого шага полезно смотреть df -h /.

1. Остановленные контейнеры

docker container prune удаляет все остановленные контейнеры. Перед этим стоит просмотреть список через docker ps -a: всё, что программа записала внутрь контейнера (не в том), исчезнет вместе с ним. Флаг -f убирает вопрос «вы уверены?». Мы ставим его в примерах, чтобы вывод получался без диалога, а вы на боевом сервере, пока не привыкли, запускайте без него и отвечайте на вопрос вручную.

Команда сообщила, что освободила 251,7 МБ, но на диске стало меньше сразу на 1,1 ГБ: вместе с контейнером chatty удалился его журнал в 947 МБ. Эти 947 МБ в отчёт Docker не попали.

2. Образы без тега

docker image prune без флагов удаляет только «висячие» образы, то есть те самые образы без имени. Это безопасно, если вам не нужен откат на предыдущую сборку: образ без тега можно запустить только по ID, и это может быть единственная копия прежней версии.

3. Кэш сборки

docker builder prune удаляет кэш промежуточных слоёв. Он безопасен, но следующая сборка пойдёт медленнее, потому что кэш нагреется заново. У нас кэша не было, и команда вернула 0B.

4. Все образы, которыми никто не пользуется

docker image prune -a идёт дальше: удаляет любой образ, который не используется ни одним контейнером, в том числе с тегом. Опасности для данных нет, но у образов будет скачивание заново, и если реестр недоступен или вы собирали образ локально без исходников, вернуть его будет нечем. Поэтому для локальных сборок проверьте, что у вас остались исходники или Dockerfile.

5. Тома: главная ловушка

В томах лежит то, ради чего Docker и поставили: базы данных, загруженные пользователями файлы, сертификаты. Контейнер можно пересоздать, а данные тома — нет. Здесь два правила.

Первое. Обычная docker volume prune по документации Docker удаляет только анонимные тома, то есть созданные автоматически без имени. В нашем тесте (Docker 29.1.3) именованные тома после неё остались на месте, и она ничего не освободила (первая команда в выводе ниже); работу с анонимными томами мы не проверяли.

Второе. Флаг -a меняет всё: docker volume prune -a удалит все тома, к которым сейчас не привязан ни один контейнер. Если контейнер базы уже удалён (например, вы выполнили container prune или compose down раньше, а порядок в этой статье как раз такой), её том считается «неиспользуемым» и будет удалён. Мы создали тома pgdata и old-uploads, и с ключом -a исчезли оба, хотя первый «изображал» базу данных:

Поэтому перед -a всегда смотрите docker volume ls и для каждого тома задавайте себе вопрос: чьи это данные. Если сомневаетесь, сначала сделайте копию тома, а потом чистите. На старых версиях Docker поведение команд с томами могло отличаться, так что на незнакомом сервере сперва проверьте docker --version и список томов.

docker system prune: что он делает и чего не делает

docker system prune — это «всё и сразу»: он удаляет остановленные контейнеры, неиспользуемые сети, образы без тега и кэш сборки. Флаг -a добавляет все неиспользуемые образы, а --volumes — тома. Мы проверили на тестовом демоне, что именно произойдёт. Создали том keepme с данными, остановленный контейнер и образ:

Без флагов команда удалила только остановленный контейнер. С -a --volumes ушёл образ, а именованный том keepme остался на месте: в нашей версии Docker --volumes именованный том не тронул. Но не стоит рассчитывать на это, как на защиту. На другой версии или в скрипте с docker volume prune -a данные пропадут, поэтому на серверах с базами данных мы советуем чистить командами по отдельности, как описано выше.

Журналы: обнулить сейчас и ограничить навсегда

Если место съел журнал, есть два действия: убрать то, что накопилось, и не дать накопиться снова.

Чтобы обнулить журнал, не удаляя контейнер, используйте truncate -s 0. Команда делает файл пустым, а сам файл остаётся на месте, поэтому Docker обычно продолжает в него писать. Мы проверили это на остановленном контейнере: до команды файл весил 766 МБ, после — 0. Для работающего контейнера метод обычно тоже работает, но мы этого не проверяли. Если в журнале есть то, что нужно разобрать, сначала сохраните его в другое место.

Чтобы журналы не росли, задайте ротацию. Docker хранит журнал в файлах, и можно ограничить размер одного файла и число файлов. Создайте или дополните /etc/docker/daemon.json (на стенде у тестового демона файл лежал в отдельной папке, путь в выводе ниже заменён на обычный): max-size — максимальный размер одного файла, max-file — сколько файлов хранить. Эти настройки применяются к новым контейнерам: уже созданные продолжают писать по старым правилам, пока вы их не пересоздадите. После правки перезапустите службу Docker командой sudo systemctl restart docker, но учтите, что это остановит работающие контейнеры, если у вас не включён live-restore.

Мы проверили настройку: перезапустили тестовый демон с таким файлом и запустили новый «болтливый» контейнер на 25 секунд. Журнал не вырос бесконечно, а разбился на три файла, в сумме 23 МБ:

Те же параметры можно задать для одного контейнера ключами --log-opt max-size=10m --log-opt max-file=3 при docker run или блоком logging: в файле Compose (подробнее о Compose в статье про несколько сервисов в Docker Compose). Эти два способа взяты из документации Docker, на стенде мы проверяли только daemon.json.

Как не дойти до этого снова

  • Ротация журналов в daemon.json — один раз и надолго. Это главная мера, потому что журнал растёт незаметно.
  • Запускайте разовые контейнеры с --rm, чтобы они удалялись сами после завершения.
  • Данные держите в именованных томах и записывайте, что в каком томе лежит. Безымянный том через месяц никто не опознает.
  • Регулярно запускайте docker system df и держите его в привычном списке проверок по серверу. Рост значения RECLAIMABLE — сигнал убрать лишнее.
  • Чистку образов без тега и остановленных контейнеров можно повесить на расписание. Как запускать команды по таймеру, мы разбирали в статье про таймеры systemd вместо cron. Тома по расписанию не чистите.

Если место занимает не Docker

Если /var/lib/docker и /var/lib/containerd небольшие, а диск полон, ищите в другом месте. Вот команда, которая показывает самые крупные каталоги первого уровня без захода на другие файловые системы: sudo du -xh --max-depth=1 / | sort -h | tail. Флаг -x не даёт du выходить за пределы одной файловой системы, а --max-depth=1 показывает только верхний уровень. Если байты свободны, но файлы не создаются, проверьте inode: об этом наша статья про «No space left on device», когда место есть. А если вместе с диском тормозит и сервер, начните с диагностики медленного сервера.

FAQ

Безопасно ли запускать docker system prune?

Без флагов он по документации удаляет остановленные контейнеры, неиспользуемые сети, образы без тега и кэш сборки; в нашем тесте тома после него остались на месте. Но удаление остановленных контейнеров стирает то, что в них записано, поэтому сначала посмотрите docker ps -a.

Почему df показывает, что диск полон, а docker system df — нет?

Чаще всего потому, что docker system df не считает журналы контейнеров. Проверьте размер файлов *-json.log в /var/lib/docker/containers.

Удалит ли docker system prune мои тома с данными?

В нашем тесте (Docker 29.1.3) именованный том с данными после команды с --volumes остался на месте. Но docker volume prune -a удаляет все неиспользуемые тома, в том числе именованные, поэтому его запускайте только после проверки списка.

Как очистить журнал контейнера без его удаления?

Командой sudo truncate -s 0 путь_к_журналу. Путь покажет docker inspect -f '{{.LogPath}}' имя_контейнера.

Применится ли ротация журналов к уже работающим контейнерам?

Нет, настройки в daemon.json действуют на контейнеры, созданные после неё. Старые нужно пересоздать.

Итоги

  • Диск Docker съедают образы, остановленные контейнеры, тома, кэш сборки и журналы. Первые четыре видно в docker system df, журналы нет.
  • Большие журналы ищите через sudo sh -c 'du -h /var/lib/docker/containers/*/*-json.log | sort -h | tail', они нередко оказываются виноватыми.
  • Чистите по порядку: контейнеры, образы без тега, кэш, образы целиком. К томам подходите последними и с проверкой.
  • docker volume prune -a удаляет все неиспользуемые тома вместе с данными, в том числе тома базы, чей контейнер уже удалён.
  • Журналы ограничьте в /etc/docker/daemon.json (max-size, max-file); настройка действует на новые контейнеры.
  • Если Docker невиноват, ищите крупные каталоги через du -xh --max-depth=1 / и проверьте inode.
PUBLISHED
2 ноября 2026 г.
AUTHOR
HIP-HOSTING
LANGUAGES
EN · RU