Как найти, что занимает место: образы, остановленные контейнеры, тома и огромные логи. Что чистить безопасно, а что нет.
Сервер стоит на 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 |
Сначала посмотрим, сколько занято на диске в целом, и сравним с каталогами 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 не виноват, смотрите последний раздел статьи.
Команда 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 МБ данных, и они завершились, но не удалились.
Запомните эту строку из вывода выше: 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 /.
docker container prune удаляет все остановленные контейнеры. Перед этим стоит просмотреть список через docker ps -a: всё, что программа записала внутрь контейнера (не в том), исчезнет вместе с ним. Флаг -f убирает вопрос «вы уверены?». Мы ставим его в примерах, чтобы вывод получался без диалога, а вы на боевом сервере, пока не привыкли, запускайте без него и отвечайте на вопрос вручную.
Команда сообщила, что освободила 251,7 МБ, но на диске стало меньше сразу на 1,1 ГБ: вместе с контейнером chatty удалился его журнал в 947 МБ. Эти 947 МБ в отчёт Docker не попали.
docker image prune без флагов удаляет только «висячие» образы, то есть те самые образы без имени. Это безопасно, если вам не нужен откат на предыдущую сборку: образ без тега можно запустить только по ID, и это может быть единственная копия прежней версии.
docker builder prune удаляет кэш промежуточных слоёв. Он безопасен, но следующая сборка пойдёт медленнее, потому что кэш нагреется заново. У нас кэша не было, и команда вернула 0B.
docker image prune -a идёт дальше: удаляет любой образ, который не используется ни одним контейнером, в том числе с тегом. Опасности для данных нет, но у образов будет скачивание заново, и если реестр недоступен или вы собирали образ локально без исходников, вернуть его будет нечем. Поэтому для локальных сборок проверьте, что у вас остались исходники или Dockerfile.
В томах лежит то, ради чего 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 — это «всё и сразу»: он удаляет остановленные контейнеры, неиспользуемые сети, образы без тега и кэш сборки. Флаг -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.
--rm, чтобы они удалялись сами после завершения.docker system df и держите его в привычном списке проверок по серверу. Рост значения RECLAIMABLE — сигнал убрать лишнее.Если /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», когда место есть. А если вместе с диском тормозит и сервер, начните с диагностики медленного сервера.
Без флагов он по документации удаляет остановленные контейнеры, неиспользуемые сети, образы без тега и кэш сборки; в нашем тесте тома после него остались на месте. Но удаление остановленных контейнеров стирает то, что в них записано, поэтому сначала посмотрите docker ps -a.
Чаще всего потому, что docker system df не считает журналы контейнеров. Проверьте размер файлов *-json.log в /var/lib/docker/containers.
В нашем тесте (Docker 29.1.3) именованный том с данными после команды с --volumes остался на месте. Но docker volume prune -a удаляет все неиспользуемые тома, в том числе именованные, поэтому его запускайте только после проверки списка.
Командой sudo truncate -s 0 путь_к_журналу. Путь покажет docker inspect -f '{{.LogPath}}' имя_контейнера.
Нет, настройки в daemon.json действуют на контейнеры, созданные после неё. Старые нужно пересоздать.
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); настройка действует на новые контейнеры.du -xh --max-depth=1 / и проверьте inode.