Ошибка overlayfs: no space left on device в Docker или containerd почти всегда появляется не вовремя: контейнер перестаёт стартовать, сборка образа падает, CI ломается, а внутри приложения начинают сыпаться ENOSPC. При этом обычная проверка через df -h нередко показывает, что свободное место как будто ещё есть.
Подвох в том, что для контейнерных рантаймов «место закончилось» означает не только нехватку байтов на файловой системе. Причиной могут быть исчерпанные inode, переполненные логи, разросшийся build cache, старые слои overlay2, неочищенные snapshot-ы containerd и удалённые, но всё ещё открытые файлы.
Ниже — практический runbook для Debian и Ubuntu: как быстро локализовать проблему, безопасно очистить мусор и не сломать рабочие контейнеры по пути.
Первое правило диагностики: всегда проверяйте и объём диска, и inode. Связка
df -hиdf -iпочти всегда даёт верную картину быстрее, чем хаотичная очистка.
Что означает ошибка и откуда она берётся
Сообщения могут выглядеть так:
overlayfs: no space left on device
write /var/lib/docker/...: no space left on device
failed to extract layer: no space left on device
containerd: failed to write: no space left on device
На практике чаще всего встречаются четыре причины:
- закончились байты на разделе, где лежит
/var/lib/dockerили/var/lib/containerd; - закончились inode, и файловая система больше не может создавать новые файлы;
- накопились неиспользуемые образы, слои, stopped-контейнеры, cache и тома;
- место удерживают уже удалённые, но открытые файлы, чаще всего логи.
Для Docker это особенно характерно при активной работе со слоями overlay2. Частые сборки, BuildKit, короткоживущие контейнеры и постоянные деплои очень быстро раздувают дерево в /var/lib/docker/overlay2. Даже если суммарный размер образов выглядит умеренным, количество мелких файлов может быть огромным.
У containerd ситуация похожая: content store, snapshot-ы и служебные данные обычно лежат в /var/lib/containerd. Если на узле работает Kubernetes, k3s или другой стек поверх containerd, рост каталога идёт не менее активно.
Сначала диагностика: где именно кончилось место
Самая частая ошибка в инциденте — сразу запускать агрессивный prune. На прод-сервере это может освободить место, но одновременно удалить полезный кэш, образы для отката или данные, которые ещё нужны.
Проверяем объём диска и inode
df -h
df -i
Смотрите прежде всего на разделы с /var, /var/lib/docker, /var/lib/containerd и /tmp. Если в df -h раздел близок к 100%, проблема в байтах. Если 100% показывает df -i, у вас типичный случай исчерпания inode.
Очень распространённый сценарий: по объёму занято только 50–60%, но inode уже кончились. Для overlayfs это выглядит ровно как обычный disk full.
Смотрим, кто занял место в /var
du -xh /var --max-depth=1 | sort -h
du -xh /var/lib --max-depth=1 | sort -h
du -xh /var/lib/docker --max-depth=1 | sort -h
du -xh /var/lib/containerd --max-depth=2 | sort -h | tail -n 30
Так можно быстро понять, виноват ли действительно Docker или containerd, либо место забрали journald, apt-кэш, временные файлы или логи приложений.
Для Docker смотрим сводку рантайма
docker system df
docker ps -a
docker images
docker volume ls
Команда docker system df особенно полезна тем, что показывает отдельно образы, контейнеры, локальные тома и build cache. Это помогает не чистить вслепую.
Для containerd проверяем snapshot-ы и content store
ctr -n k8s.io containers ls
ctr -n k8s.io images ls
ctr -n k8s.io snapshots ls
ctr -n k8s.io content ls
Если у вас используется не k8s.io, а другой namespace, подставьте его явно. Это частая причина путаницы, когда кажется, что в containerd почти пусто, а место продолжает исчезать.
Ищем удалённые, но занятые файлы
lsof +L1 | sort -k7 -n
Если процесс держит большой уже удалённый файл, du его не покажет, но место не освободится. Особенно часто это происходит с логами контейнеров и приложений после неудачной ротации.
Если у вас контейнеры крутятся на небольшом сервере и каталог /var/lib/docker стабильно растёт, иногда проще заранее вынести такую нагрузку на отдельный VDS с нормальным запасом по диску, чем регулярно тушить аварии на переполненном системном разделе.

Безопасная очистка Docker
Если виноват именно Docker, идите от самого безопасного варианта к более агрессивному. Ручное удаление каталогов внутри /var/lib/docker почти всегда плохая идея.
Удаляем stopped-контейнеры, dangling-образы и неиспользуемые сети
docker system prune
docker system prune -f
Это базовая и сравнительно безопасная очистка. Она не удаляет используемые тома и образы, но часто уже освобождает заметный объём.
Чистим build cache
docker builder prune
docker builder prune -a
На build-хостах именно кэш сборки часто оказывается главным потребителем места. Ключ -a удаляет весь неиспользуемый кэш, а не только dangling-слои.
Удаляем неиспользуемые образы
docker image prune -a
Эта команда агрессивнее: она удаляет все образы, не привязанные к работающим контейнерам. Если вы рассчитываете на локальный быстрый rollback, сначала проверьте, что именно удаляете.
Осторожно с томами
Тома могут содержать базы данных, загрузки пользователей, файловый кэш приложения и прочие важные данные.
docker volume ls
docker volume prune
Удаляйте только заведомо неиспользуемые тома. Если сомневаетесь, лучше сначала проверить привязку к контейнерам и содержимое.
Самая дорогая по последствиям ошибка при аварийной очистке — удалить volumes в надежде быстро освободить место. Инцидент с диском пройдёт, но может начаться уже инцидент с потерей данных.
Проверяем логи контейнеров
Если используется драйвер json-file, логи в /var/lib/docker/containers могут занимать десятки гигабайт.
du -xh /var/lib/docker/containers --max-depth=2 | sort -h | tail -n 30
Для аварийного освобождения места можно обнулить крупные лог-файлы:
find /var/lib/docker/containers -name '*-json.log' -type f -size +100M -exec truncate -s 0 {} ;
После этого обязательно настройте ротацию, иначе проблема вернётся. Пример /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
systemctl restart docker
Учтите, что новые параметры логирования обычно применяются только к заново созданным контейнерам. Текущие контейнеры часто требуется пересоздать.
Что делать, если раздулся overlay2
Если основной объём сидит в /var/lib/docker/overlay2, почти всегда речь о слоях образов, writable-слоях старых контейнеров или build cache. Удалять каталоги внутри overlay2 вручную нельзя: так легко повредить состояние Docker.
- Проверьте, какие контейнеры реально работают.
- Сверьте цифры через
docker system df. - Запустите
docker system prune. - При необходимости очистите кэш через
docker builder prune -a. - Удалите ненужные образы через
docker image prune -a. - Проверьте deleted-файлы через
lsof +L1.
Если место в overlay2 растёт постоянно, пересмотрите политику сборок и хранения локальных image. На CI-хостах полезно также ограничить накопление промежуточных артефактов. Если в вашей инфраструктуре много обратных прокси и кэша, пригодится и отдельная оптимизация дисковых данных, например через разбор кэширования Redis и Memcached для PHP.
Иногда правильное решение — вынести data-root Docker на отдельный раздел большего объёма:
systemctl stop docker
rsync -aHAXx /var/lib/docker/ /srv/docker/
mkdir -p /etc/docker
{
"data-root": "/srv/docker"
}
mv /var/lib/docker /var/lib/docker.bak
systemctl start docker
Такой перенос лучше делать в окно обслуживания и только после проверки, что контейнеры поднимаются корректно.
Очистка containerd без ручной порчи snapshot store
С containerd нужно быть аккуратнее, особенно на Kubernetes-узлах. Самое плохое решение — удалять файлы прямо внутри служебных каталогов без понимания, кто и как их использует.
Смотрим, что занимает место
du -xh /var/lib/containerd --max-depth=2 | sort -h | tail -n 50
Обычно основной объём сидит в каталогах io.containerd.snapshotter.v1.overlayfs и io.containerd.content.v1.content.
Если используется nerdctl
nerdctl system df
nerdctl image prune -a
nerdctl container prune
nerdctl volume prune
Для standalone-сценариев это самый удобный путь: логика похожа на Docker, но не забывайте учитывать namespace.
Если это Kubernetes-узел
На Kubernetes лучше использовать инструменты уровня CRI и kubelet:
crictl images
crictl ps -a
crictl rmi --prune
Если узел уже испытывает disk pressure, проблема может быть не только в containerd, но и в общей нехватке места под логи, временные файлы и образы. В этом случае полезно проверять не только runtime, но и системные компоненты, включая настройку логов и кэшей веб-стека, если на узле есть фронтенд-сервисы. Для Nginx-площадок рядом по теме может быть полезен разбор кэширования subrequest и SSI в Nginx.
Ручное удаление каталогов внутри /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs допустимо только как крайняя мера, после остановки зависимых сервисов и с полным пониманием последствий.

Когда виноваты не байты, а inode
Если df -i показывает 100%, то удаление одного большого файла почти не поможет. Нужно сокращать количество файлов, а не только их размер.
Типичные источники исчерпания inode:
- слои образов и build cache;
- разросшиеся каталоги логов;
- временные директории CI;
- артефакты сборки вроде
node_modules,vendorи package cache; - контейнерные данные с огромным числом мелких файлов.
Быстро оценить масштаб можно так:
find /var/lib/docker -xdev -printf '.' | wc -c
find /var/lib/containerd -xdev -printf '.' | wc -c
find /var/log -xdev -printf '.' | wc -c
Если inode регулярно заканчиваются именно на контейнерных хостах, имеет смысл пересмотреть разметку диска и отделить контейнерные данные от системного раздела. Для небольших проектов это особенно важно даже на компактном VDS, где запас по /var невелик.
Что ещё проверить на Debian и Ubuntu
journald и системные логи
journalctl --disk-usage
journalctl --vacuum-time=7d
journalctl --vacuum-size=500M
Если journald не ограничен, он может незаметно занять значительную часть /var.
apt-кэш
apt clean
Обычно это не главный источник проблемы, но в аварийной ситуации каждый освобождённый гигабайт полезен.
Временные файлы
du -xh /tmp --max-depth=1 | sort -h
du -xh /var/tmp --max-depth=1 | sort -h
Некоторые пайплайны, распаковка архивов и сборки образов активно используют именно эти каталоги.
Пошаговый аварийный runbook
- Проверьте
df -hиdf -i. - Посмотрите распределение объёма через
duв/var,/var/lib/dockerи/var/lib/containerd. - Найдите deleted-файлы командой
lsof +L1. - Для Docker начните с
docker system pruneиdocker builder prune. - Проверьте и при необходимости сократите логи контейнеров.
- Для containerd используйте
crictl,ctrилиnerdctlв зависимости от стека. - После освобождения места перепроверьте запуск контейнеров и состояние приложения.
- Сразу после инцидента настройте профилактику: ротацию логов, регулярную очистку cache и мониторинг inode.
Главное правило: не удаляйте вручную внутренние каталоги overlayfs, если проблему можно решить штатными средствами рантайма. Быстрый путь тут часто оказывается самым рискованным.
Профилактика: как не ловить ENOSPC снова
Ограничьте логи контейнеров
Для Docker с json-file обязательно задайте max-size и max-file. Для containerd и Kubernetes проверьте политику логирования на уровне вашего стека.
Регулярно чистите build cache
На CI-хостах полезен cron или systemd timer для плановой очистки. Важно подобрать retention так, чтобы не убить скорость сборок без необходимости.
Мониторьте и байты, и inode
Одна только метрика свободного места не даёт полной картины. Для контейнерных хостов это обязательный минимум наблюдаемости.
Разделяйте system и container data
Если сервер изначально работает как контейнерный хост, лучше держать данные рантайма на отдельном разделе. Тогда переполнение overlay2 не уронит весь системный /var.
Пересмотрите lifecycle локальных образов
На узле должны оставаться только те image, которые реально нужны для текущей работы и отката. Остальное лучше загружать из registry по необходимости.
Настройте алерты заранее
- свободное место меньше 15%;
- свободные inode меньше 15%;
- аномальный рост
/var/lib/dockerили/var/lib/containerd; - слишком большие логи контейнеров;
- disk pressure на Kubernetes-узлах.
Итог
Ошибка overlayfs: no space left on device в Docker и containerd почти никогда не лечится одной магической командой. Нужна короткая диагностика: df -h, df -i, du, проверка runtime и поиск deleted-файлов.
Для Docker обычно помогают штатные очистки, контроль логов и build cache. Для containerd важно использовать правильный namespace и корректные инструменты — ctr, crictl или nerdctl. А если вы упёрлись в inode, нужно убирать не самые большие файлы, а самые массовые наборы мелких файлов.
И главное: если не ограничить логи, не навести порядок в кэше и не мониторить inode, тот же инцидент повторится снова — как обычно, в самый неудобный момент.


