Короткий ответ
Драйвер json-file по умолчанию не ротирует. Каждый контейнер пишет /var/lib/docker/containers/<id>/<id>-json.log без потолка. На host.example включите max-size и max-file глобально в daemon.json и/или в compose сервиса web. Существующие гигабайтные файлы сами не усохнут — после политики нужен recreate или аккуратный truncate. Не docker system prune -a (это образы). Не удаляйте app_data.
Карта «кто съел диск», если не уверены: диск из‑за Docker.
Симптомы и как отличить
sudo du -sh /var/lib/docker/containersсравнимо сdf;docker logs app-web-1минутами открывается;docker system dfпоказывает маленький Images, большой — не volumes, а «прочее» на ФС;- DEBUG-уровень приложения в проде.
| Источник | Признак | Не путать |
|---|---|---|
| json.log | файлы *-json.log | overlay |
| journald драйвер | диск в /var/log/journal | этот рецепт другой |
| приложение пишет в volume | рост app_data | не logging driver |
Возможные причины
- Нет
log-optsв daemon.json. - Compose без секции
logging:, контейнер создан до глобальной политики (opts не ретроактивны). - Сервис в цикле печатает stacktrace (restart loop).
- access-лог nginx в stdout на высокой RPS без лимита.
- Кто-то включил
docker logs -fв systemd навсегда — не размер файла, но нагрузка. - Редко: драйвер
localvs json-file путают в документации.
Диагностика
1. Размер логов
sudo du -sh /var/lib/docker/containers
sudo find /var/lib/docker/containers -name '*-json.log' -printf '%s %p\n' | sort -n | tailСопоставьте id с именем:
docker ps -aq | while read id; do
n=$(docker inspect -f '{{.Name}} {{.HostConfig.LogConfig.Type}} {{.HostConfig.LogConfig.Config}}' "$id")
s=$(sudo stat -c%s /var/lib/docker/containers/$id/$id-json.log 2>/dev/null || echo 0)
echo "$s $n"
done | sort -n2. Политика
python3 -m json.tool /etc/docker/daemon.json
cd /opt/app && docker compose config | grep -A10 logging || echo 'no logging in yaml'3. Темп роста
sudo ls -l /var/lib/docker/containers/$(docker inspect -f '{{.Id}}' app-web-1)/$(docker inspect -f '{{.Id}}' app-web-1)-json.log
sleep 10
sudo ls -l /var/lib/docker/containers/$(docker inspect -f '{{.Id}}' app-web-1)/$(docker inspect -f '{{.Id}}' app-web-1)-json.logРост МБ/10с — чините уровень логов приложения, не только ротацию.
Решение
Сценарий A. Глобальная ротация Engine 27.x
sudo cp -a /etc/docker/daemon.json /etc/docker/daemon.json.bak.$(date +%F)Добавьте (смержите с существующим JSON, не затрите storage-driver):
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}sudo python3 -m json.tool /etc/docker/daemon.json >/dev/null
sudo systemctl reload docker || sudo systemctl restart dockerУже созданные контейнеры не подхватят opts. Recreate стека:
cd /opt/app
docker compose up -d --force-recreate
docker inspect app-web-1 --format '{{json .HostConfig.LogConfig}}'app_data сохранится. Не down -v.
Сценарий B. Только сервис web в compose
services:
web:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"Полезно, если daemon.json общий и спорный.
Сценарий C. Уже 40 ГБ лога, диск 100%
- Включить политику как выше.
- Остановить только жирный контейнер.
- Truncate после stop или recreate:
id=$(docker inspect -f '{{.Id}}' app-web-1)
sudo systemctl reload docker 2>/dev/null || true
# после compose stop web:
sudo truncate -s 0 /var/lib/docker/containers/$id/$id-json.log
docker compose -f /opt/app/compose.yaml start webЕсли stop нельзя и inode держит место — recreate быстрее.
Не find ... -delete по всем json.log на хосте с чужими стеками без списка.
Сценарий D. Приложение-болтун
Ротация — предохранитель, не лечение. Срежьте DEBUG. Иначе 3×10 MiB будут крутиться как вентилятор, CPU на json тоже есть.
Политика в daemon.json действует на новые контейнеры после reload/restart docker. live-restore позволяет reload без убийства процессов, но LogConfig у уже созданных не меняется. Поэтому обязателен compose up -d --force-recreate без -v. Проверка: inspect max-size. Если забыли recreate, диск снова упрётся в тот же json.log.
Драйвер local в 27.x ротирует по умолчанию (около 100m×5, уточняйте docker info и документацию текущей патч-версии, не держите цифру как вечную). Миграция json-file → local в инциденте — лишний риск: сначала opts на json-file, место вернуть, драйвер менять отдельно с тестом docker logs.
Не направляйте docker logs в cron каждую минуту без --since: вы читаете весь файл. Для сбора в syslog используйте драйвер или vector/promtail, читающий тот же json с ротацией, либо journald. Stdout приложения на DEBUG с телами запросов за час заполняет 10m×3 и вы теряете след инцидента — режьте уровень, не max-size до гигабайт «на всякий случай».
Если контейнер в restart loop, ротация не спасёт CPU: json-file синхронно пишет каждый crash. Чините exit, параллельно включите max-size.
Как проверить, что проблема устранена
docker inspect app-web-1 --format '{{json .HostConfig.LogConfig}}'
sudo find /var/lib/docker/containers -name '*-json.log' -printf '%s\n' | awk '{s+=$1} END {print s}'
df -h /var/lib/dockerВ LogConfig видны max-size/max-file. Сумма логов ограничена порядком N контейнеров × 30 MiB (при 10m×3), не десятки гигабайт. Стек жив.
Если не помогло
- После daemon.json inspect без opts — не было recreate.
- Драйвер
journald: ротация systemd-journald, не json-file. Смотритеjournalctl --disk-usage. localdriver имеет свою ротацию; не смешивайте советы.- Место не вернулось: deleted-open,
lsof +L1, restart контейнера. - Заполняет не log, а overlay/volume — вернитесь к карте диска.
Профилактика
log-optsв эталоне daemon.json всех docker-хостов.- Шаблон compose с logging.
- Алерт на размер
*-json.log> 200 MiB. - Не логировать тела запросов с PII в stdout.
- CI grep: отсутствие max-size на новых стеках.
FAQ
json-file vs journald vs local
json-file — простой, нуждается в max-size. journald — в journald лимиты хоста. local — бинарный, с дефолтной ротацией в современных Engine; если уже json-file в проде, проще включить opts, чем менять драйвер в инциденте.
max-file 3 мало для разбора?
Для инцидента увеличьте до 10×20m на одном сервисе, не снимайте потолок.
truncate на живом файле?
Риск. Предпочтительнее stop/recreate. Если truncate на живом — сразу проверьте docker logs --tail 5.
prune чистит json.log?
Нет. prune про неиспользуемые объекты. Жирный лог живого контейнера prune не трогает.
syslog драйвер?
Тогда ротация на приёмнике. Не оставляйте json-file без opts «пока не настроим syslog».