Как Docker тихо сожрал 105 ГБ на Btrfs-корне моего ThinkPad
Хомлаб на старом ThinkPad T460 с корнем на Btrfs в 217 ГБ остался без места. Виновник — безлимитные логи Docker из одного контейнера GitLab. Как я это нашёл, починил и захардил систему.
У меня хомлаб на старом ThinkPad T460 — Arch Linux, корень на Btrfs (/dev/sda2, 217 ГБ), внешний диск на 3.7 ТБ для медиа. Это не стойка, это ноутбук на полке, который делает вещи. GitLab, Navidrome, Deluge, Audiobookshelf, Calibre-Web — стандартный самохостный набор.
За месяцы я ещё накопил сервисов, которые попробовал один раз и забыл: OpenWebUI, Stremio, TubeArchivist, WireGuard (wg-easy), Portainer, Agent-Zero. Каждый оставил за собой образы, тома и контейнеры, которые продолжали работать, хотя я давно перестал ими пользоваться.
Однажды btrfs balance и btrfs scrub начали падать с No space left on device. Тут стало интересно.
Обманчивая ошибка
Первый сюрприз: df -h не показывал полный диск. Свободное место вроде было. Но Btrfs работает не как ext4 — пространство выделяется чанками, и можно исчерпать метаданные, пока данные ещё свободны. Файловая система рапортует о свободном месте, но операционно вы в тупике.
btrfs filesystem usage /Вот эта команда показала реальную картину: метаданные почти заполнены, чанки фрагментированы, и Docker — самый большой потребитель всего.
Нахождение настоящей проблемы
sudo du -h --max-depth=1 /var/lib/dockerРезультат:
105G /var/lib/docker/containers15G /var/lib/docker/volumes5.9G /var/lib/docker/overlay2126G /var/lib/docker126 ГБ. На диске в 217 ГБ. Docker в одиночку съедал 58% всего корневого раздела.
Разбивка ясная: containers с 105 ГБ — это слон в комнате. Не образы, не тома — записываемые слои контейнеров, что на практике означает одно: логи.
sudo du -sh /var/lib/docker/containers/*/Один каталог выделялся — контейнер GitLab (2cb12163b9be...) писал неограниченные JSON-логи месяцами. Драйвер логирования Docker по умолчанию — json-file без ограничения размера. Каждая строка stdout/stderr каждого контейнера пишется в файл -json.log, который растёт бесконечно.
Один контейнер, который щедро логирует, может генерировать гигабайты в неделю. За месяцы непрерывной работы этот конкретный контейнер произвёл больше 100 ГБ лог-файлов.
Аварийное исправление
Первый приоритет — освободить место немедленно.
sudo sh -c 'truncate -s 0 /var/lib/docker/containers/*/*-json.log'Это обнуляет каждый лог-файл контейнера, не останавливая ни один контейнер. Безопасно — Docker продолжит писать в тот же file handle, просто файл начнётся заново с нуля.
Результат: /var/lib/docker/containers упал со 105 ГБ до 176 КБ. Мгновенное облегчение.
Постоянное исправление: ротация логов
Аварийное исправление даёт время. Постоянное — сказать Docker ротировать свои логи:
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }}sudo systemctl restart dockerЭто ограничивает каждый контейнер 3 лог-файлами по 10 МБ — максимум 30 МБ на контейнер. Для хомлаба с 10 работающими контейнерами это 300 МБ в худшем случае, а не 100+ ГБ.
Важный нюанс: это применяется только к контейнерам, созданным после изменения конфига. Существующие контейнеры сохраняют свою старую конфигурацию логирования. Если хотите применить везде — пересоздайте контейнеры (docker compose down && docker compose up -d).
Чистка: убираем мёртвый груз
После решения проблемы с логами пришла очередь сервисов, которые я накопил и забыл:
Удалил:
- OpenWebUI — попробовал раз, больше не открывал
- Stremio — заменён другими решениями
- TubeArchivist — классная идея, на практике не пользовался
- wg-easy / WireGuard — перешёл на другой подход
- Portainer — и так работаю через CLI
- Agent-Zero — эксперимент, заброшен
Оставил:
- GitLab — реально использую каждый день
- Navidrome — музыкальный сервер, незаменим
- Deluge — торрент-клиент
- Audiobookshelf — только по требованию
- Calibre-Web — только по требованию
Команды чистки:
# Остановить и удалить конкретные контейнерыdocker stop <container> && docker rm <container>
# Удалить все неиспользуемые образы, контейнеры, сети и томаdocker system prune -a --volumes
# Удалить конкретные осиротевшие томаdocker volume rm <volume_name>docker system prune -a --volumes — агрессивная команда: она убирает всё, что не прикреплено к работающему контейнеру. На хомлабе, где вы уже решили, что остаётся, а что уходит — это именно то, что нужно.
Восстановление Btrfs
После чистки Docker сам Btrfs всё ещё требовал внимания. Аллокация чанков была фрагментирована после месяцев больших записей и удалений:
sudo btrfs balance start -dusage=50 /Эта команда ребалансирует чанки данных, заполненные менее чем на 50%, объединяя фрагментированное свободное место в полезные чанки. На вращающемся диске это занимает время, но после этого btrfs balance и btrfs scrub перестали жаловаться на место.
Финальное состояние
df -h /Filesystem Size Used Avail Use%/dev/sda2 217G 135G 78G 62%Со 126 ГБ Docker-раздувания до комфортных 62%. Система снова дышит.
Чему я научился
1. Docker-логи могут тихо сожрать 100+ ГБ, если не ограничены. Нет предупреждения, нет алерта, нет уведомления от демона. Файл логов просто растёт, пока диск не заполнится. Это поведение Docker по умолчанию. На сервере с терабайтами хранилища можно не замечать годами. На ноутбучном диске в 217 ГБ это занимает месяцы.
2. docker system prune НЕ чинит взрыв логов.
Prune удаляет неиспользуемые образы, остановленные контейнеры, висячие тома. Он не трогает логи работающих контейнеров. Можно пруновать весь день — 105-гигабайтный файл логов не уменьшится ни на байт.
3. В Btrfs «нет свободного места» может случиться, когда df показывает свободное место.
Btrfs управляет пространством чанками. Можно исчерпать чанки метаданных, при том что данные ещё свободны. Ошибка реальна, даже если цифры выглядят неправильно. btrfs filesystem usage / — команда, которая говорит правду.
4. Всегда настраивайте ротацию логов в daemon.json при первой установке Docker.
Это должно быть частью каждой настройки Docker, сразу после apt install или pacman -S. 10-строчный JSON-файл предотвращает целый класс проблем, которые неприятно отлаживать задним числом.
5. Хомлаб — это не продакшн-сервер. Пруньте агрессивно.
Если вы не открывали сервис 2-4 недели — удалите или переведите на docker compose up/down по требованию. Мёртвые контейнеры стоят дискового места, памяти и ментальных усилий на отслеживание того, что реально работает.
Чеклист
Для будущей справки (или если вы в той же ситуации):
df -h+docker system df+sudo du -sh /var/lib/docker— понять, где вы находитесьsudo du -h --max-depth=1 /var/lib/docker | sort -h— найти главного виновникаsudo sh -c 'truncate -s 0 /var/lib/docker/containers/*/*-json.log'— аварийная чистка логов- Создать
/etc/docker/daemon.jsonс ротацией логов — постоянное исправление docker system prune -a --volumes— убрать всё неиспользуемоеsudo btrfs balance start -dusage=50 /— отбалансировать чанки Btrfs (если на Btrfs)df -h+docker system df— проверить результат
Правило: если что-то не использовалось 2-4 недели — удалите или переведите на on-demand compose.