Ротация логов Docker: не дай логам забить диск VPS
Стандартный драйвер логов Docker хранит данные без ограничений. Один активный контейнер может забить диск VPS на 50 ГБ за несколько дней. В этом руководстве настраиваем глобальную ротацию логов, переопределения для отдельных сервисов в Compose, автоматическую очистку и мониторинг дискового пространства.
По умолчанию Docker не ограничивает размер логов. Каждая строка, которую контейнер пишет в stdout или stderr, сохраняется на диск навсегда. На VPS с 25 или 50 ГБ хранилища один болтливый контейнер может сожрать всё свободное место за считанные дни.
Это руководство решает проблему. Ты настроишь глобальную ротацию логов, зададишь лимиты для отдельных сервисов в Docker Compose, автоматизируешь очистку диска и настроишь мониторинг, чтобы проблема не застала врасплох.
Все команды проверены на Debian 12 и Ubuntu 24.04 с Docker Engine 28.x/29.x.
Что понадобится:
- VPS с Debian 12 или Ubuntu 24.04 и установленным Docker
- SSH-доступ с пользователем sudo
- Базовое знакомство с Docker и Docker Compose
Почему логи контейнеров Docker забивают диск?
Стандартный драйвер логов Docker — json-file. Он перехватывает всё, что контейнер пишет в stdout и stderr, и сохраняет в формате JSON в /var/lib/docker/containers/<container-id>/<container-id>-json.log. По умолчанию максимального размера нет, ротации нет. Файл растёт, пока диск не заполнится.
Приложение на Node.js с уровнем логирования INFO генерирует примерно 50 МБ в день. Reverse proxy (обратный прокси) при умеренном трафике может выдавать 200 МБ и больше. На VPS с 50 ГБ это значит, что один неуправляемый контейнер способен израсходовать всё свободное место за несколько недель.
Когда диск заполняется, ломается всё сразу: контейнеры не могут писать, базы данных падают, SSH-сессии зависают, и ты даже не можешь залогиниться, чтобы починить.
Как проверить текущее использование диска
Прежде чем что-то менять, оцени масштаб проблемы:
df -h /var/lib/docker
Эта команда покажет, сколько места занимает каталог данных Docker. Затем получи разбивку по Docker:
docker system df
Ожидаемый вывод:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 3 1.2GB 450MB (37%)
Containers 8 4 3.8GB 3.1GB (81%)
Local Volumes 3 2 500MB 120MB (24%)
Build Cache 12 0 800MB 800MB (100%)
Строка «Containers» показывает размер лог-файлов. Если это число непропорционально большое, проблема в логах.
Для детальной разбивки по контейнерам:
docker system df -v
Эта команда перечислит каждый контейнер с размером его логов. Найди виновников.
Как найти самые большие лог-файлы напрямую
Если docker system df подтвердил проблему, найди конкретные логи, которые жрут место:
sudo find /var/lib/docker/containers/ -name "*-json.log" -exec ls -sh {} + | sort -rh | head -10
Эта команда выведет 10 самых больших лог-файлов контейнеров с их размерами.
Экстренные меры: освободи место прямо сейчас
Если диск уже заполнен или почти заполнен, сначала реши непосредственную проблему, а потом настраивай ротацию.
Обрежь лог-файл конкретного контейнера (без остановки контейнера):
sudo truncate -s 0 /var/lib/docker/containers/<container-id>/<container-id>-json.log
Замени <container-id> на реальный ID контейнера из docker ps --no-trunc -q.
Чтобы обрезать все лог-файлы Docker разом:
sudo sh -c 'truncate -s 0 /var/lib/docker/containers/*/*-json.log'
Проверь, что место освободилось:
df -h /var/lib/docker
Это временная мера. Логи снова вырастут. Следующие разделы сделают исправление постоянным.
Как настроить ротацию логов в Docker daemon.json?
Файл daemon.json задаёт настройки логирования по умолчанию для всех новых контейнеров. Драйвер json-file поддерживает max-size (максимальный размер лог-файла до ротации) и max-file (количество ротированных файлов). Все значения в log-opts должны быть строками, даже числа.
Создай или отредактируй конфигурацию демона:
sudo nano /etc/docker/daemon.json
Если файл не существует, создай его. Если он уже содержит конфигурацию (например, пользовательские реестры или DNS), добавь ключи log-driver и log-opts рядом с существующими.
Рекомендации по размерам для разных дисков VPS
Выбирай значения в зависимости от размера диска VPS:
| Размер диска VPS | max-size |
max-file |
Макс. лог на контейнер | Обоснование |
|---|---|---|---|---|
| 25 ГБ | 5m |
3 |
15 МБ | Мало места; логи по минимуму |
| 50 ГБ | 10m |
5 |
50 МБ | Стандартный VPS; баланс |
| 100 ГБ | 25m |
5 |
125 МБ | Много места; дольше хранить |
| 200 ГБ+ | 50m |
5 |
250 МБ | Большой диск; расширенная отладка |
Для VPS на 50 ГБ (самый распространённый вариант) используй такую конфигурацию:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
Сохрани файл, затем перезапусти Docker:
sudo systemctl restart docker
Проверь, что новые настройки активны:
docker info --format '{{.LoggingDriver}}'
Ожидаемый вывод:
json-file
Убедись, что параметры логирования применяются к новым контейнерам, запустив тестовый:
docker run -d --name log-test alpine echo "test" && docker inspect --format '{{.HostConfig.LogConfig}}' log-test && docker rm log-test
Ожидаемый вывод:
{json-file map[max-file:5 max-size:10m]}
Что происходит с запущенными контейнерами после изменения daemon.json?
Существующие контейнеры сохраняют свою первоначальную конфигурацию логирования. Новые настройки применяются только к контейнерам, созданным после перезапуска. Запущенные контейнеры нужно пересоздать, чтобы они подхватили новые настройки.
Если используешь Docker Compose:
docker compose down && docker compose up -d
Для standalone-контейнеров останови и удали их, затем запусти заново. Флаг --force-recreate тоже работает:
docker compose up -d --force-recreate
Проверь, что конкретный контейнер использует новую конфигурацию:
docker inspect --format '{{.HostConfig.LogConfig}}' <container-name>
Ожидаемый вывод:
{json-file map[max-file:5 max-size:10m]}
Как задать лимиты логов в Docker Compose?
Конфигурация логирования для отдельного сервиса в Docker Compose переопределяет настройки из daemon.json. Это позволяет задать болтливым сервисам более жёсткие лимиты, а тихим — больше запаса.
Добавь блок logging под любой сервис:
services:
web:
image: nginx:alpine
logging:
driver: json-file
options:
max-size: "5m"
max-file: "3"
ports:
- "80:80"
api:
image: node:22-alpine
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
ports:
- "3000:3000"
Чтобы не повторять блок logging в каждом сервисе, используй YAML anchor (якорь):
x-logging: &default-logging
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
services:
web:
image: nginx:alpine
<<: *default-logging
ports:
- "80:80"
api:
image: node:22-alpine
<<: *default-logging
ports:
- "3000:3000"
worker:
image: myapp/worker:latest
logging:
driver: json-file
options:
max-size: "50m"
max-file: "3"
Сервис worker переопределяет якорь своими собственными лимитами. Все остальные сервисы получают общую конфигурацию.
Примени конфигурацию:
docker compose up -d --force-recreate
Проверь:
docker inspect --format '{{.HostConfig.LogConfig}}' web
В чём разница между драйверами логов json-file, local и journald?
Docker поставляется с тремя драйверами, которые хранят логи на хосте. У каждого свои компромиссы. Драйвер json-file используется по умолчанию. Драйвер local — рекомендованная Docker замена для экономии места. Драйвер journald интегрируется с журналом systemd.
| Характеристика | json-file | local | journald |
|---|---|---|---|
| Ротация по умолчанию | Нет | Да (100 МБ всего) | Управляется journald |
| Сжатие | Опционально (compress: "true") |
Включено по умолчанию | Управляется journald |
Поддержка docker logs |
Да | Да | Да |
| Формат логов | JSON (человекочитаемый) | Бинарный (внутренний) | Бинарный (journald) |
| Доступ внешним инструментам | Легко (текстовые файлы) | Не поддерживается | Через journalctl |
| max-size по умолчанию | Без ограничений | 20 МБ на файл | Задаётся в journald.conf |
| max-file по умолчанию | 1 (без ротации) | 5 файлов | Н/Д |
Когда использовать каждый драйвер
json-file — безопасный выбор по умолчанию. Работает везде, поддерживает docker logs, а файлы логов — обычный JSON, который любой инструмент может распарсить. Добавь max-size и max-file, и он отлично подойдёт для большинства VPS.
local лучше для экономии диска. Сжатие включено по умолчанию, ротация встроена (5 файлов по 20 МБ = 100 МБ на контейнер), и ничего настраивать не нужно. Компромисс: лог-файлы используют внутренний бинарный формат. Внешние сборщики логов, которые читают файлы напрямую (вроде Filebeat в файловом режиме), не смогут их распарсить. Если ты читаешь логи только через docker logs или отправляешь их через плагин логирования Docker, переходи на local.
journald — правильный выбор, если ты уже используешь журнал systemd для всех остальных сервисов и хочешь логи контейнеров в одном месте. Ротация управляется собственной конфигурацией journald (/etc/systemd/journald.conf). Логи читаешь через journalctl вместо docker logs (хотя docker logs тоже работает).
Как переключиться на драйвер local
Отредактируй /etc/docker/daemon.json:
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
Перезапусти Docker:
sudo systemctl restart docker
Проверь:
docker info --format '{{.LoggingDriver}}'
Ожидаемый вывод:
local
Пересоздай контейнеры, чтобы применить новый драйвер:
docker compose up -d --force-recreate
Как использовать драйвер journald
Отредактируй /etc/docker/daemon.json:
{
"log-driver": "journald"
}
Перезапусти Docker:
sudo systemctl restart docker
Читай логи конкретного контейнера:
sudo journalctl CONTAINER_NAME=mycontainer --no-pager -n 50
Следи за логами в реальном времени:
sudo journalctl CONTAINER_NAME=mycontainer -f
Ротация journald настраивается в /etc/systemd/journald.conf. Ключевые параметры:
[Journal]
SystemMaxUse=500M
SystemMaxFileSize=50M
MaxRetentionSec=7day
После редактирования перезапусти journald:
sudo systemctl restart systemd-journald
Как автоматизировать очистку Docker через cron или systemd timers?
Ротация логов не даёт отдельным контейнерам расти бесконечно. Но Docker также накапливает остановленные контейнеры, неиспользуемые образы, зависший build cache (кэш сборки) и осиротевшие сети. docker system prune всё это чистит.
Что именно удаляет docker system prune?
По умолчанию docker system prune удаляет:
- Все остановленные контейнеры
- Все сети, не используемые ни одним запущенным контейнером
- Все dangling-образы (неотмеченные образы, на которые не ссылается ни один контейнер)
- Весь неиспользуемый build cache
Он не удаляет:
- Запущенные контейнеры
- Именованные тома (данные твоих баз в безопасности)
- Отмеченные образы, на которые есть ссылки
- Образы, используемые запущенными контейнерами
Флаг --all дополнительно удаляет все неиспользуемые образы (не только dangling). Флаг --volumes добавляет анонимные тома к очистке. Используй --volumes с осторожностью: он уничтожает данные в анонимных томах.
Вариант 1: задание cron
Создай еженедельное задание очистки:
sudo crontab -e
Добавь:
0 3 * * 0 /usr/bin/docker system prune -f >> /var/log/docker-prune.log 2>&1
Это запускается каждое воскресенье в 03:00. Флаг -f пропускает запрос подтверждения. Вывод записывается в лог-файл для аудита.
Проверь, что crontab сохранился:
sudo crontab -l
Вариант 2: таймер systemd (рекомендуется)
Таймеры systemd надёжнее cron. Они пишут в журнал, обрабатывают пропущенные запуски (если сервер был выключен) и проще в мониторинге.
Создай сервисный юнит:
sudo nano /etc/systemd/system/docker-prune.service
[Unit]
Description=Docker system prune
Wants=docker.service
After=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/docker system prune -f --filter "until=168h"
Флаг --filter "until=168h" очищает только объекты старше 7 дней. Это защищает недавно остановленные контейнеры, которые ты, возможно, захочешь проверить.
Создай таймер:
sudo nano /etc/systemd/system/docker-prune.timer
[Unit]
Description=Run Docker prune weekly
[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target
Persistent=true означает, что если сервер был выключен в запланированное время, задание выполнится при следующей загрузке. RandomizedDelaySec распределяет нагрузку, если у тебя несколько серверов.
Включи и запусти таймер:
sudo systemctl daemon-reload
sudo systemctl enable --now docker-prune.timer
enable сохраняет таймер после перезагрузки. --now запускает его сразу.
Проверь, что таймер активен:
sudo systemctl status docker-prune.timer
Посмотри, когда он сработает в следующий раз:
sudo systemctl list-timers docker-prune.timer
Запусти вручную для теста:
sudo systemctl start docker-prune.service
Проверь результат:
sudo journalctl -u docker-prune.service --no-pager -n 20
Как безопасно очистить тома Docker?
Тома хранят постоянные данные: базы данных, загрузки, конфигурации. Здесь нужна осторожность.
Выведи список всех томов и их использование:
docker volume ls
Покажи только тома, не привязанные ни к одному контейнеру:
docker volume ls -f dangling=true
Удали висячие тома:
docker volume prune -f
Эта команда удаляет только тома, не используемые ни одним контейнером (запущенным или остановленным). Именованные тома, привязанные к остановленным контейнерам, в безопасности.
Проверь, что осталось:
docker volume ls
Никогда не запускай docker volume prune сразу после docker system prune --volumes. System prune с --volumes уже обрабатывает очистку томов. Запускать оба — в лучшем случае избыточно.
Чтобы удалить конкретный том, который ты определил как ненужный:
docker volume rm <volume-name>
Всегда проверяй, какие данные содержит том, перед удалением:
docker volume inspect <volume-name>
Поле Mountpoint показывает, где данные лежат на диске. Можно посмотреть содержимое:
sudo ls -la $(docker volume inspect --format '{{.Mountpoint}}' <volume-name>)
Как мониторить использование диска Docker на VPS?
Автоматический мониторинг предотвращает сюрпризы. В этом разделе настраиваем пороговое оповещение, которое проверяет использование диска Docker и отправляет предупреждение при превышении лимита.
Быстрая ручная проверка
Запусти эти две команды, когда хочешь получить снимок состояния:
df -h /var/lib/docker
docker system df
Для детальной разбивки по контейнерам и образам:
docker system df -v
Скрипт автоматического оповещения
Создай скрипт мониторинга:
sudo nano /usr/local/bin/docker-disk-alert.sh
#!/bin/bash
# Alert when Docker's partition exceeds a usage threshold
THRESHOLD=80
MAILTO="admin@example.com"
USAGE=$(df /var/lib/docker | awk 'NR==2 {gsub(/%/,""); print $5}')
if [ "$USAGE" -ge "$THRESHOLD" ]; then
DOCKER_DF=$(docker system df 2>&1)
DISK_DF=$(df -h /var/lib/docker 2>&1)
TOP_LOGS=$(find /var/lib/docker/containers/ -name "*-json.log" -exec ls -sh {} + 2>/dev/null | sort -rh | head -5)
BODY="Docker disk usage on $(hostname) is at ${USAGE}%.
Disk usage:
${DISK_DF}
Docker breakdown:
${DOCKER_DF}
Largest log files:
${TOP_LOGS}"
echo "$BODY" | mail -s "ALERT: Docker disk at ${USAGE}% on $(hostname)" "$MAILTO"
logger -t docker-disk-alert "Docker disk usage at ${USAGE}% - alert sent"
fi
Установи права:
sudo chmod 750 /usr/local/bin/docker-disk-alert.sh
Проверь права:
ls -la /usr/local/bin/docker-disk-alert.sh
Ожидаемый вывод:
-rwxr-x--- 1 root root 612 Mar 19 12:00 /usr/local/bin/docker-disk-alert.sh
Скрипт требует mailutils (или mailx) для отправки email. Установи, если нет:
sudo apt install -y mailutils
Протестируй скрипт:
sudo /usr/local/bin/docker-disk-alert.sh
Если использование диска ниже порога, ничего не произойдёт. Чтобы протестировать отправку оповещения, временно установи THRESHOLD=1 в скрипте, запусти его, затем верни обратно.
Настрой оповещение через таймер systemd
Создай сервис:
sudo nano /etc/systemd/system/docker-disk-alert.service
[Unit]
Description=Check Docker disk usage
[Service]
Type=oneshot
ExecStart=/usr/local/bin/docker-disk-alert.sh
Создай таймер:
sudo nano /etc/systemd/system/docker-disk-alert.timer
[Unit]
Description=Check Docker disk usage every 6 hours
[Timer]
OnCalendar=*-*-* 00/6:00:00
Persistent=true
[Install]
WantedBy=timers.target
Включи:
sudo systemctl daemon-reload
sudo systemctl enable --now docker-disk-alert.timer
Проверь:
sudo systemctl list-timers docker-disk-alert.timer
Устранение неполадок
Диск заполнен и Docker не запускается
Если Docker отказывается запускаться из-за полностью заполненного диска:
sudo truncate -s 0 /var/lib/docker/containers/*/*-json.log
sudo systemctl start docker
Затем немедленно настрой ротацию логов, как описано выше.
Синтаксическая ошибка в daemon.json не даёт Docker запуститься
Docker не запустится, если daemon.json содержит невалидный JSON. Проверь файл:
sudo python3 -m json.tool /etc/docker/daemon.json
Если команда выведет отформатированный JSON, синтаксис верный. Если выведет ошибку, исправь указанную строку.
Проверь сообщение об ошибке Docker:
sudo journalctl -u docker.service -n 20 --no-pager
Логи продолжают расти после настройки ротации
Ротация применяется только к новым контейнерам. Существующие контейнеры сохраняют свою первоначальную конфигурацию. Пересоздай их:
docker compose up -d --force-recreate
Проверь, что новая конфигурация вступила в силу:
docker inspect --format '{{.HostConfig.LogConfig}}' <container-name>
docker system prune не освободил много места
docker system prune не трогает запущенные контейнеры, их логи и именованные тома. Если проблема именно в логах, обрежь лог-файлы или настрой ротацию. Если проблема в томах, используй docker volume prune после проверки, что нужные данные не будут потеряны.
Проверь, что занимает место:
sudo du -sh /var/lib/docker/*
Эта команда разобьёт использование по подсистемам Docker: containers (логи), overlay2 (образы/слои), volumes и другие.
Итоги
Полная настройка управления логами Docker на VPS состоит из четырёх уровней:
- Глобальная ротация в
/etc/docker/daemon.jsonсmax-sizeиmax-fileпредотвращает неограниченный рост логов для всех контейнеров. - Переопределения для сервисов в Docker Compose дают болтливым сервисам более жёсткие лимиты.
- Автоматическая очистка через
docker system pruneна таймере systemd удаляет мёртвые контейнеры, неиспользуемые образы и build cache. - Мониторинг диска со скриптом оповещения ловит проблемы до того, как они станут сбоями.
После настройки проверь, что всё работает: посмотри docker inspect на контейнерах, запусти docker system df для подтверждения текущего использования и дождись, пока таймер prune сработает хотя бы раз. Проверь journalctl -u docker-prune.service, чтобы убедиться, что он отработал.
Авторское право 2026 Virtua.Cloud. Все права защищены. Данный контент является оригинальным произведением команды Virtua.Cloud. Воспроизведение, повторная публикация или распространение без письменного разрешения запрещены.