SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor

Как очистить место на диске Docker на VPS

Узнайте, как безопасно освободить пространство на сервере, занятое образами, контейнерами и кэшем сборки. Пошаговое руководство по использованию docker system prune без потери данных.

Определите, что занимает место на диске, прежде чем что-либо удалять

Docker занимает место на VPS в четырех местах: образы, остановленные контейнеры, кэш сборки и локальные тома. Сначала выполните docker system df, чтобы выяснить, что именно расходует пространство, а затем примените наиболее точную команду очистки. Порядок действий важен, так как последняя команда в этом руководстве, docker volume prune -a, удаляет данные без возможности восстановления.

Начните с файловой системы, а не с Docker.

df -h /
sudo du -xh --max-depth=1 /var/lib/docker | sort -h

df покажет общую картину. du укажет, куда ушло место. Флаг -x ограничивает du одной файловой системой, чтобы команда не переходила на другие разделы и не учитывала их дважды. Здесь важны пять директорий: overlay2 содержит слои образов и контейнеров, volumes — данные томов, containers — метаданные контейнеров и файлы логов, buildkit — кэш сборки, а image — метаданные слоев.

Заметка о sudo и подстановочных знаках (wildcards) оболочки, так как это часто приводит к потере времени. Директория /var/lib/docker принадлежит пользователю root и недоступна для чтения обычному пользователю, поэтому ls /var/lib/docker возвращает Permission denied. Команда вида sudo du -sh /var/lib/docker/* также завершится ошибкой, потому что оболочка раскрывает * до запуска sudo, а у оболочки нет прав на чтение этой директории. Все команды ниже используют find или --max-depth вместо подстановочных знаков именно по этой причине.

Теперь посмотрим на ситуацию глазами Docker.

docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          24        6         12.4GB    8.91GB (71%)
Containers      31        6         1.42GB    1.39GB (97%)
Local Volumes   14        5         6.03GB    4.11GB (68%)
Build Cache     212       0         9.87GB    9.87GB

Эти цифры относятся к одной конкретной машине и не отражают состояние вашей системы. Обратите внимание на структуру вывода. TOTAL подсчитывает объекты, ACTIVE — те, что используются прямо сейчас, а RECLAIMABLE — это оценка Docker того, сколько места можно освободить в данной категории.

Два момента в RECLAIMABLE часто сбивают с толку. Команда учитывает общие слои образов для каждого образа, который их использует, поэтому строка с образами обычно обещает больше места, чем вы получите на самом деле. Кроме того, она никогда не включает файлы логов контейнеров, так как Docker не считает их объектами, подлежащими очистке. Если du показывает, что директория занимает гораздо больше места, чем утверждает docker system df, причина кроется в лог-файлах; ниже есть раздел, посвященный этому.

Добавьте -v для получения детальной информации по каждому объекту.

docker system df -v

Это разделяет сводку на секции по типам объектов. Секция образов добавляет колонки SHARED SIZE и UNIQUE SIZE, чтобы вы могли увидеть реальную стоимость отдельного образа. Секция томов добавляет счетчик LINKS — это количество контейнеров, подключенных к данному тому. Помните про LINKS: значение 0 является главным критерием того, что команды очистки томов применятся к этому объекту.

Висячие образы (dangling) против неиспользуемых образов (unused)

Эти два термина кажутся взаимозаменяемыми, но это не так. Фильтры работают по-разному, поскольку объекты различаются.

Висячий образ — это образ без тега. Он отображается как <none> в выводе docker images. Вы создаете такой образ при каждой пересборке: docker build -t myapp:latest . переносит тег myapp:latest на новый образ, а старый образ сохраняет все свои слои, но теряет имя. Ничто на него не ссылается, и система не удаляет его автоматически.

Неиспользуемый образ — это любой образ, с тегом или без, на который в данный момент не ссылается ни один контейнер. Образ postgres:16, который вы скачали в прошлом месяце и который сейчас не запущен, является неиспользуемым, но не является висячим.

docker image prune        # dangling images only
docker image prune -a     # every image no container refers to

Вторая команда сначала запрашивает подтверждение.

WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N]

Внимательно прочитайте этот запрос. Фраза «связанные с ними» означает существующий объект контейнера, запущенный или остановленный. Если вы выполнили docker compose down, контейнеры удаляются, поэтому каждый образ, который использовали эти сервисы, становится неиспользуемым, и -a удаляет их все. Вы не потеряете ничего, что нельзя восстановить, но следующая команда docker compose up -d приведет к повторной загрузке или пересборке всех образов, что потребует пропускной способности сети и времени сборки на небольшом VPS. Это одна из практических причин, по которой стоит знать что удаляет docker compose down и что остается после stop, прежде чем выполнять очистку.

Фильтр позволяет исключить из области действия недавние образы.

docker image prune -a --filter "until=240h"

Эта команда удаляет неиспользуемые образы, созданные более 240 часов (10 дней) назад, и оставляет более новые без изменений. Значение until принимает строку длительности Go, например 240h, или абсолютную временную метку, такую как 2026-08-01T00:00:00.

Что такое кэш сборки и почему он бесконечно растет

BuildKit — это механизм сборки, который Docker использует по умолчанию для docker build и docker compose build начиная с версии Docker Engine 23.0. Он кэширует результат каждого шага любого выполняемого Dockerfile и хранит этот кэш в /var/lib/docker/buildkit. Благодаря кэшу повторная сборка завершается за секунды, то есть он выполняет свою задачу. Проблема в том, что по умолчанию старые записи не удаляются. Если собрать один и тот же образ пятьдесят раз с шагом COPY, который каждый раз меняется, в системе останется пятьдесят наборов слоев.

Кэш сборки невидим для docker image prune. Это отдельный тип объектов со своей собственной командой.

docker builder prune                        # dangling cache
docker builder prune -a                     # all unused cache
docker builder prune --filter until=168h    # cache untouched for 7 days

Ни одна из этих команд не затрагивает ваши образы или данные. Единственное последствие очистки кэша сборки — следующая сборка пройдет медленно. На VPS, где образы пересобираются регулярно, Build Cache часто занимает наибольший объем в docker system df, что делает его самым безопасным крупным объектом для удаления.

Команды очистки в порядке от безопасных к деструктивным

Выполняйте команды по списку и остановитесь, как только df -h / снова будет выглядеть исправно. Каждая команда выводит строку Total reclaimed space: по завершении работы.

  1. docker container prune удаляет остановленные контейнеры. Их записываемые слои также удаляются, поэтому всё, что контейнер записал вне тома, будет стерто вместе с ним. Тома не затрагиваются.
  2. docker image prune удаляет только «висячие» (dangling) образы. Это самая безопасная команда для работы с образами.
  3. docker builder prune удаляет «висячий» кэш сборки. Платой за это будет одна медленная сборка.
  4. docker image prune -a удаляет все образы, на которые не ссылается ни один контейнер. Платой за это будет повторная загрузка или пересборка.
  5. docker system prune выполняет первые три действия одновременно и добавляет удаление неиспользуемых сетей.
  6. docker volume prune удаляет неиспользуемые анонимные тома.
  7. docker volume prune -a удаляет неиспользуемые тома, включая именованные. Именно эта команда удаляет базы данных.

docker system prune выводит область своего действия перед запуском.

WARNING! This will remove:
        - all stopped containers
        - all networks not used by at least one container
        - all dangling images
        - unused build cache
Are you sure you want to continue? [y/N]

Тома намеренно отсутствуют в этом списке. Добавление --volumes возвращает анонимные тома в область действия. Добавление -a расширяет этап очистки образов с «висячих» до всех неиспользуемых. Полная команда docker system prune -a --volumes -f на продуктовом сервере — это способ, которым люди теряют данные при попытке освободить место.

Почему очистка томов приводит к удалению базы данных

Этот раздел стоит прочитать дважды.

Том считается неиспользуемым, если к нему не подключен ни один контейнер. Это единственный критерий. Docker не проверяет, пуст ли том, объявлен ли он в файле compose и является ли он единственной копией вашей базы данных. LINKS 0 в docker system df -v означает возможность удаления, и ничего более.

Теперь рассмотрим два обычных действия подряд. Вы запускаете docker compose down для чистой перезагрузки стека. Это удаляет контейнеры, но оставляет именованные тома на месте, что в точности соответствует документации. Ваш том Postgres теперь ни к чему не подключен. Через десять минут вы запускаете docker volume prune -a, чтобы освободить место, и база данных исчезает. Обе команды отработали корректно. Эта последовательность действий привела к уничтожению данных.

Начиная с Docker Engine 23.0 (версия API 1.42), обычная команда стала более узкоспециализированной, чем раньше.

WARNING! This will remove anonymous local volumes not used by at least one container.

Анонимный том — это том, который Docker создал для вас, обычно потому, что образ объявляет VOLUME, а вы не дали ему имя. Обычно в таких томах хранятся данные, которые вы не просили сохранять. Именованный том, который вы прописали в файле compose, удаляется только при добавлении -a. Старые версии Docker удаляли оба типа томов одной командой, поэтому не полагайтесь на привычки, сформированные на сервере, который вы с тех пор обновили. Разница становится понятной, только когда вы знаете чем именованные тома отличаются от bind mounts, поскольку bind mount вообще не является томом Docker, и ни одна команда очистки его не затронет.

Смотрите, что удаляете. Замените myapp_pgdata на имя тома, который вы проверяете.

docker volume ls -f dangling=true
docker volume inspect myapp_pgdata
sudo ls -la /var/lib/docker/volumes/myapp_pgdata/_data

Фильтр dangling=true для томов означает «без ссылок», а не «пустой». Вывод _data показывает, что находится внутри на самом деле. Если вы обнаружите там каталог pgdata или mysql, остановитесь и сделайте копию, прежде чем продолжать. Такое же уничтожение данных происходит при выполнении docker compose down -v, которая удаляет все тома, объявленные в файле compose, не запрашивая подтверждения.

Том — это единственная сущность на хосте Docker, которую невозможно восстановить при пересборке. Именно поэтому данные томов должны находиться в резервной копии restic, которая выполняется вне сервера, где ошибочно введенный флаг не сможет их повредить.

Когда очистка не помогает: файлы логов контейнеров

Вы выполнили очистку, docker system df показывает, что освобождать практически нечего, но диск всё ещё переполнен. Проверьте логи.

sudo find /var/lib/docker/containers -name '*-json.log' -exec du -h {} + | sort -h | tail -10

Каждый контейнер записывает свой стандартный вывод и стандартный поток ошибок в JSON-файл в директории /var/lib/docker/containers/. При установке по умолчанию параметр max-size не задан, что означает отсутствие ограничений. В результате контейнер, попавший в цикл аварийных завершений, будет записывать данные до тех пор, пока раздел не окажется полностью заполнен. Ни одна команда очистки не удаляет эти файлы, так как контейнеры, которые их создают, запущены, а значит, по определению не подлежат удалению.

Не удаляйте файл. Выполнение rm для открытого файла лога ничего не освободит, так как Docker daemon удерживает открытый файловый дескриптор, а ядро сохраняет выделенные блоки до закрытия этого дескриптора. Показатель df не изменится. Вместо этого выполните усечение (truncate), что позволит сохранить тот же inode и даст демону возможность продолжить запись.

sudo find /var/lib/docker/containers -name '*-json.log' -exec truncate -s 0 {} +
df -h /

Это временное решение. Команда docker logs для этих контейнеров теперь не вернёт ничего, но файлы сразу начнут расти снова. Настоящее решение — ротация, которая описана в следующем разделе.

Измеряйте показатели до и после, каждый раз

Никогда не гадайте, что именно изменила очистка. Снимите показания, выполните команду, снимите показания снова.

df -h /
docker system df
docker builder prune -f --filter until=168h
docker image prune -f
docker system df
df -h /

Сравните два вывода df. Это единственное число, которое определяет, продолжит ли ваш сервер обслуживать запросы. docker system df затем покажет, какая именно строка изменилась, а каждая очистка выводит собственное значение Total reclaimed space:.

Если df не изменилось, но docker system df сообщает об освобождении места, значит, открытый файловый дескриптор удерживает удаленные блоки — это та самая проблема с лог-файлами, описанная выше. Если оба показателя изменились, но диск снова заполняется в течение дня, у вас проблема с ростом данных, а не с их очисткой. Решением будет ротация логов и настройка запланированного задания.

Как предотвратить повторное заполнение диска

Ограничьте размер логов. Создайте или отредактируйте /etc/docker/daemon.json.

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Это ограничит размер логов каждого контейнера до 30 MB. Все значения в log-opts должны быть строковыми, включая числовые. Проверьте корректность синтаксиса файла перед перезапуском, так как поврежденный daemon.json не позволит демону запуститься и приведет к остановке всех контейнеров.

python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -i 'logging driver'

docker info теперь должен возвращать Logging Driver: json-file. Сами лимиты отображаются в секции LogConfig файла docker inspect для контейнеров, созданных после перезапуска. Это важная деталь: настройки применяются только к новым контейнерам. Существующие контейнеры сохраняют конфигурацию, с которой были созданы, поэтому их необходимо пересоздать.

docker compose up -d --force-recreate

Тот же лимит можно задать для конкретного сервиса в файле compose. Это лучший выбор, если одному «шумному» сервису требуется индивидуальное ограничение.

services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Настройте периодическую очистку. Еженедельно, с ограничением на висящие (dangling) образы и старый кэш сборки. Никогда не используйте -a или --volumes в автоматизированных задачах, так как задача, запущенная в момент простоя стека, удалит его образы, а при использовании --volumes это приведет к потере данных.

sudo tee /etc/cron.weekly/docker-prune >/dev/null <<'EOF'
#!/bin/sh
docker image prune -f
docker builder prune -f --filter until=168h
EOF
sudo chmod +x /etc/cron.weekly/docker-prune
sudo /etc/cron.weekly/docker-prune

Последняя строка запускает скрипт вручную, чтобы вы могли увидеть вывод перед тем, как он начнет работать без присмотра. Файл должен быть исполняемым, а его имя не должно содержать точек, так как run-parts игнорирует неисполняемые файлы и файлы с расширениями.

Настройте оповещение о свободном месте. Очистка после того, как диск заполнился — это аварийное восстановление. Оповещение при достижении 80 процентов — это профилактика.

usage=$(df --output=pcent / | tr -dc '0-9')
[ "$usage" -ge 80 ] && echo "root filesystem at ${usage} percent full"

Добавьте это в cron вместе с используемой вами системой уведомлений. Свободное место — это лишь часть картины, поэтому дополните оповещение мониторингом состояния диска на вашем VPS, так как выход диска из строя и его переполнение одинаково останавливают контейнеры, но требуют разных способов устранения.

Все вышеперечисленное предполагает стандартную установку с корневым каталогом данных в /var/lib/docker. Если вы переместили его с помощью ключа data-root в daemon.json, подставляйте свой путь в каждой команде. Правильная настройка структуры каталогов на новом сервере — часть процесса настройки Docker на VPS, и это гораздо проще сделать до того, как 40 GB контейнеров окажутся на неподходящем разделе.

FAQ

Удаляет ли docker system prune мои тома?

Нет. Обычная команда удаляет остановленные контейнеры, неиспользуемые сети, «висящие» образы и неиспользуемый кэш сборки; запрос на подтверждение перед выполнением перечисляет именно эти объекты. Тома затрагиваются только при добавлении флага --volumes, причем начиная с Docker Engine 23.0 этот флаг касается только анонимных томов, а не именованных. Именованные тома удаляются командами docker volume prune -a и docker compose down -v. Именно с этими двумя командами следует проявлять осторожность.

Почему диск всё ещё заполнен после выполнения docker prune?

Обычно существует две причины. Первая — файлы логов контейнеров в /var/lib/docker/containers/, которые не затрагиваются ни одной командой prune и растут бесконечно, пока вы не настроите max-size. Вторая — удалённый файл, который всё ещё открыт процессом: если вы удалили лог командой rm во время работы контейнера, демон удерживает дескриптор файла, и ядро не освобождает блоки, поэтому df не показывает изменений. Сравните вывод sudo du -xh --max-depth=1 /var/lib/docker и docker system df, чтобы понять, с чем именно вы столкнулись.

В чём разница между docker image prune и docker image prune -a?

Обычная команда удаляет только «висящие» образы, то есть те, которые потеряли свой тег (обычно в результате пересборки). Вариант с -a удаляет все образы, на которые не ссылается ни один существующий контейнер, включая тегированные образы, которые вы загрузили намеренно. После docker compose down контейнеры исчезают, поэтому -a удалит и образы этого стека. Ничто не теряется безвозвратно, так как при следующем запуске они будут загружены или собраны заново, но при медленном соединении это может занять много времени.

Как предотвратить заполнение диска логами Docker?

Установите max-size и max-file в секции log-opts файла /etc/docker/daemon.json, затем перезапустите демон командой sudo systemctl restart docker. Настройки применяются только к контейнерам, созданным после перезапуска, поэтому пересоздайте работающие контейнеры с помощью docker compose up -d --force-recreate. Вы можете задать те же две опции для каждого сервиса в файле compose в ключе logging; это рекомендуется, если один сервис генерирует значительно больше логов, чем остальные.

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

Обычная команда docker system prune -f безопасна на хосте, где все стеки постоянно запущены, но она удаляет остановленные контейнеры, поэтому она уничтожит контейнер, который вы остановили намеренно и планировали запустить позже. Более безопасный вариант для планировщика — docker image prune -f вместе с docker builder prune -f --filter until=168h: это освобождает два наиболее быстрорастущих раздела и не затрагивает тома. Никогда не добавляйте в расписание -a или --volumes.