SSD Nodes Learn 🎉 VPS від $5.50/міс
Посібники Matt ConnorВід Matt Connor

Docker prune: як звільнити місце на VPS

Диск VPS заповнений через Docker? Перевірте образи, контейнери, кеш і томи, визначте джерело зайнятого місця та безпечно застосуйте потрібну prune-операцію.

З’ясуйте, що займає місце на диску, перш ніж видаляти дані

Docker займає місце на диску VPS у чотирьох категоріях: образи, зупинені контейнери, кеш збірки та локальні томи. Спочатку виконайте docker system df, щоб визначити, яка категорія займає місце, а потім застосуйте найвужчу операцію prune, яка його звільнить. Порядок має значення, оскільки остання команда в цьому посібнику, 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 і шаблони shell, оскільки вони часто спричиняють проблеми. Каталог /var/lib/docker належить root і недоступний для читання звичайному користувачу, тому ls /var/lib/docker повертає Permission denied. Команда на кшталт sudo du -sh /var/lib/docker/* також завершується помилкою, оскільки shell розгортає * ще до запуску sudo, а shell не може прочитати цей каталог. Саме тому в усіх наведених нижче командах замість шаблону використовуються 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 щодо обсягу, який можна звільнити командою prune для цього рядка.

У RECLAIMABLE є два моменти, які часто спричиняють непорозуміння. Спільні шари образів підраховуються окремо для кожного образу, який їх використовує, тому рядок образів зазвичай показує більший обсяг, ніж реально можна звільнити. Крім того, ця команда ніколи не враховує файли журналів контейнерів, оскільки Docker не вважає файл журналу об’єктом, який можна звільнити. Якщо du показує каталог, значно більший за значення, яке вказує docker system df, причиною є файли журналів. Нижче наведено окремий розділ про це.

Додайте -v, щоб отримати розподіл за окремими об’єктами.

docker system df -v

Зведені дані буде поділено на окремий розділ для кожного типу об’єктів. У розділі образів з’являться стовпці SHARED SIZE і UNIQUE SIZE, щоб можна було побачити фактичну вартість одного образу. У розділі томів з’явиться стовпець LINKS, який показує кількість контейнерів, підключених до цього тому. Запам’ятайте LINKS, оскільки значення 0 є єдиною перевіркою, яку застосовують команди очищення томів.

Образи без тегу та невикористані образи

Ці два терміни здаються взаємозамінними, але це не так. Фільтри працюють по-різному, оскільки йдеться про різні об’єкти.

Образ без тегу — це образ, якому не призначено тег. У docker images він відображається як <none>. Під час кожної повторної збірки створюється такий образ: 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, тому це найбільший об’єкт, який можна безпечно видалити.

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

Виконуйте команди по черзі та зупиніться, щойно df -h / знову працюватиме нормально. Після завершення кожна команда виводить рядок Total reclaimed space:.

  1. docker container prune видаляє зупинені контейнери. Їхні доступні для запису шари також видаляються, тому всі дані, записані контейнером за межами volume, буде видалено разом із контейнером. Volumes не змінюються.
  2. docker image prune видаляє лише dangling images. Це найбезпечніша команда для очищення images.
  3. docker builder prune видаляє dangling build cache. Єдиний наслідок — наступна збірка триватиме довше.
  4. docker image prune -a видаляє всі images, на які не посилається жоден контейнер. Наслідком буде повторне завантаження або повторна збірка.
  5. docker system prune виконує перші три операції одночасно та додатково видаляє невикористовувані networks.
  6. docker volume prune видаляє невикористовувані anonymous volumes.
  7. docker volume prune -a видаляє невикористовувані volumes, зокрема named volumes. Саме ця команда видаляє бази даних.

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 навмисно не включено до цього списку. Додавання --volumes включає anonymous volumes до області дії. Додавання -a розширює операцію з images: замість dangling images вона охоплює всі невикористовувані images. Повний docker system prune -a --volumes -f на production host — це спосіб втратити дані під час спроби звільнити місце.

Чому очищення томів видаляє базу даних

Цей розділ варто прочитати двічі.

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

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

Починаючи з Docker Engine 23.0 (API version 1.42), звичайна команда має вужчу дію, ніж раніше.

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

Анонімний том створює Docker, зазвичай тому, що image оголошує VOLUME, а ви не вказали йому ім’я. Зазвичай у таких томах зберігаються дані, які ви не планували зберігати. Іменований том, який ви записали у compose-файлі, видаляється лише після додавання -a. Старі версії Docker видаляли обидва типи звичайною командою, тому не покладайтеся на звички, сформовані на сервері, який згодом оновили. Ця відмінність стає зрозумілою лише після того, як ви дізнаєтеся чим іменовані томи відрізняються від bind mounts, оскільки bind mount взагалі не є томом Docker, і жодна команда prune його не зачепить.

Перевірте перед видаленням. Замініть 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 host, що неможливо відтворити під час повторного розгортання. Саме тому дані томів потрібно зберігати в резервній копії 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 не задано, тобто розмір журналу необмежений. Тому один контейнер, що застряг у циклі аварійного перезапуску, може записувати дані, доки розділ не заповниться. Команда prune не видаляє ці файли, оскільки контейнери, які їх створюють, запущені. За визначенням, такі контейнери не можна видалити цією командою.

Не видаляйте файл. Виконання rm для відкритого файлу журналу нічого не звільняє, оскільки daemon Docker все ще утримує відкритий файловий дескриптор, а ядро зберігає блоки виділеними, доки цей дескриптор не буде закрито. df зовсім не зменшиться. Натомість очистіть файл через truncate. Це збереже той самий inode і дасть daemon змогу продовжити запис.

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

Це тимчасове рішення. docker logs для цих контейнерів тепер нічого не повертає, а файли одразу знову починають збільшуватися. Справжнє рішення — ротація журналів. Її розглянуто в наступному розділі.

Вимірюйте до і після кожної операції

Не здогадуйтеся, що саме зробила операція prune. Спочатку виконайте вимірювання, потім одну команду, а після цього повторіть вимірювання.

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 показує, який саме рядок змінився, а кожна операція prune виводить власне значення 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 повністю зупиняє запуск daemon і разом із ним виводить з ладу всі контейнери.

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"

Налаштуйте вибіркове очищення. Запускайте його щотижня лише для невикористовуваних образів і старого build cache. Ніколи не додавайте -a або --volumes до запланованого завдання, оскільки завдання, запущене під час зупинки stack, видалить образи цього stack, а з --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 і використайте наявний notifier. Вільне місце — лише половина проблеми, тому поєднайте це сповіщення з моніторингом стану диска на 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, daemon зберігає файловий дескриптор, а ядро не звільняє блоки. Тому 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 заповнювати диск?

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

Чи безпечно запускати docker system prune із cron job?

Звичайна команда docker system prune -f безпечна на хості, де всі стеки постійно працюють. Проте вона видаляє зупинені контейнери. Тому буде видалено контейнер, який ви навмисно зупинили та планували запустити пізніше. Безпечніший запланований job — це docker image prune -f разом із docker builder prune -f --filter until=168h. Він звільняє два ресурси, які зростають найшвидше, і не може торкнутися томів. Ніколи не плануйте виконання -a або --volumes.