Логирование в Docker: лучшие практики
Через несколько месяцев работы контейнеров сервер вдруг сообщает, что диск закончился, хотя вы ничего крупного не заливали и не разворачивали. В девяти случаях из десяти виноваты логи контейнеров: Docker по умолчанию пишет их без единого ограничения по размеру. Разберём, почему так происходит, как это лечится в одну строчку конфига и как удобно смотреть логи, когда контейнеров становится больше одного.
Содержание
Почему диск заканчивается сам по себе
У каждого контейнера в Docker есть драйвер логирования — механизм, который решает, куда уходит вывод из stdout и stderr процесса внутри контейнера. Если вы никогда не трогали настройки, используется драйвер по умолчанию — json-file. Он построчно пишет каждое сообщение в JSON-файл на хост-системе, обычно по пути /var/lib/docker/containers/<container-id>/<container-id>-json.log.
Ключевая проблема в том, что до недавнего времени и во многих типовых установках json-file работает без ротации и без лимита размера. Файл растёт, пока жив контейнер и пока он что-то пишет в вывод. Приложение, которое логирует каждый HTTP-запрос, каждое SQL-соединение или, тем более, падает в цикле с трассировкой стека — способно раздуть один лог-файл до нескольких гигабайт за пару недель. А если на сервере крутится десяток контейнеров, эффект накапливается незаметно: место убывает медленно, никто не смотрит на df -h каждый день, и однажды база не может записать транзакцию, потому что раздел заполнен на 100%.
Это классическая грабля именно для новичков, потому что она не проявляется сразу. При первом запуске docker run или docker compose up всё работает отлично — логов пока мало. Проблема стреляет через недели или месяцы эксплуатации, когда сервер уже в проде и разбираться с местом приходится в панике. Проверить, не ваш ли это случай, можно одной командой:
du -sh /var/lib/docker/containers/*/*-json.log | sort -h
Если видите файлы по несколько гигабайт — вы нашли причину. Экстренно освободить место можно, не удаляя файл (иначе процесс продолжит писать в удалённый дескриптор, а место не вернётся до перезапуска контейнера):
truncate -s 0 /var/lib/docker/containers/<id>/<id>-json.log
Но это лечит симптом на один раз. Дальше — правильная настройка, чтобы грабля не повторилась.
Ограничение размера логов через daemon.json
Правильное решение — задать лимит на размер и количество файлов ротации для всего Docker-демона сразу, а не для каждого контейнера вручную. Это делается в файле /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
max-size— максимальный размер одного файла лога перед ротацией (например,10m— 10 мегабайт).max-file— сколько файлов ротации хранить. Приmax-file: 3Docker держит текущий файл плюс два архивных, старые удаляются автоматически.
С такими значениями на контейнер уйдёт максимум 30 МБ логов (10m × 3) вместо неограниченного роста. Подберите цифры под свою нагрузку: для тихого сервиса хватит 5m/2, для шумного API с частыми запросами разумнее 20m/5, чтобы не терять историю за последние часы при разборе инцидента.
После правки daemon.json перезапустите демон:
systemctl restart docker
Важный нюанс: этот параметр в daemon.json задаёт лимит по умолчанию для новых контейнеров — на уже запущенные он не распространяется задним числом. Пересоздайте существующие контейнеры (docker compose up -d --force-recreate или docker rm + повторный run), чтобы лимит применился к ним тоже.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSОграничение логов в docker-compose
Если вы управляете сервисами через docker-compose.yml, лимит можно и нужно задавать точечно — для конкретного сервиса, без изменения общесистемного daemon.json. Это удобнее, когда у сервисов разная интенсивность логирования:
services:
api:
image: myapp/api:latest
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
worker:
image: myapp/worker:latest
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
Такая настройка в compose-файле имеет приоритет над глобальными опциями из daemon.json для этого конкретного сервиса. Если хотите задать одинаковый лимит сразу для всех сервисов проекта, не дублируя блок в каждом, вынесите его через YAML-якорь:
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
api:
image: myapp/api:latest
logging: *default-logging
worker:
image: myapp/worker:latest
logging: *default-logging
Это обычная практика для production-конфигураций docker-compose — лимит на логи стоит включать в шаблон с самого начала, а не добавлять постфактум, когда диск уже заполнился.
Команда docker logs и полезные флаги
Даже с ротацией логи никуда не деваются — их всё равно нужно читать при отладке. Базовая команда:
docker logs <container>
Она выводит весь накопленный лог контейнера разом, что неудобно для долго живущих сервисов. На практике почти всегда нужны флаги:
--tail N— показать только последние N строк, напримерdocker logs --tail 100 api.--follow(или-f) — следить за логом в реальном времени, какtail -f:docker logs -f api.--since— показать записи начиная с момента времени:docker logs --since 2026-08-29T10:00:00 apiили относительно,docker logs --since 30m api.--until— обратный флаг, отсечь записи после указанного момента; полезно при разборе инцидента с известными границами по времени.--timestamps(или-t) — добавить метку времени к каждой строке, если приложение сама её не пишет.
Комбинация, которая закрывает большинство задач отладки:
docker logs -f --tail 200 --since 1h api
Показывает последний час, начиная со 200 последних строк, и продолжает стримить новые записи. Для docker-compose аналогично работает docker compose logs -f --tail 100 api, только команда сразу понимает имена сервисов из docker-compose.yml, а не идентификаторы контейнеров.
Куда уходят логи после ротации
Стоит понимать: max-size/max-file ограничивают место на диске, но не увеличивают глубину истории — старые записи безвозвратно удаляются при ротации. Если вам важно расследовать инцидент недельной давности, а лимит хранит только последние 30 МБ за пару часов активного трафика, этого будет недостаточно.
Здесь есть развилка. Можно увеличить max-file, чтобы хранить больше архивных файлов — это просто, но линейно расходует диск и не даёт удобного поиска: чтобы найти нужную ошибку, придётся руками просматривать несколько сжатых файлов на хосте. Второй вариант — сменить драйвер логирования на что-то, что сразу пишет логи наружу, например journald (если хост использует systemd) вместо json-file, что снимает часть нагрузки с файловой системы контейнера, но не решает вопрос централизации и долгого хранения.
Когда переходить на централизованный сбор логов
Пока у вас один-два контейнера на одном сервере, docker logs с флагами --tail/--follow/--since полностью закрывает потребности. Но с ростом числа сервисов проявляются типичные боли:
- Инцидент задевает несколько контейнеров сразу, и приходится вручную сопоставлять по времени логи из
docker logs api,docker logs worker,docker logs nginx— открывая каждый в отдельном терминале. - Контейнер, в котором произошла ошибка, уже пересоздан или удалён — а вместе с ним потерян и его лог-файл, если он не был вынесен наружу.
- Логи разбросаны по нескольким серверам (например, api на одной ноде, воркеры — на другой), и
sshна каждую машину по очереди становится рутиной при каждом разборе бага. - Нужен полнотекстовый поиск по всем логам сразу — по ID запроса, по имени пользователя, по коду ошибки — а не
grepпо файлу за файлом.
Как только хотя бы один из этих пунктов стал регулярной болью — пора выносить логи в централизованную систему, куда контейнеры пишут вывод через драйвер логирования (или через агент-сборщик рядом с Docker), а вы смотрите и ищете по всем сервисам из одного интерфейса. Разворачивание такого стека — отдельная большая тема, и мы подробно разбирали практический вариант на связке Grafana + Loki в статье про логи в Grafana Loki на сервере: там же разбор типичных ошибок при её настройке в статье про частые ошибки с Grafana Loki. Если же диск на сервере уже заполнен логами и нужно быстро разобраться, что произошло — в первую очередь пригодится статья как читать логи и находить причину сбоя.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSЧастые вопросы
Почему диск на сервере заканчивается сам, если я не заливал файлы?
Почти всегда виноват json-file драйвер Docker без лимита — он пишет вывод контейнеров в файл без ротации. Проверьте du -sh /var/lib/docker/containers/*/*-json.log | sort -h и настройте max-size/max-file.
Настройка max-size в daemon.json подействует на уже запущенные контейнеры?
Нет, лимит применяется только к новым контейнерам. Существующие нужно пересоздать: docker compose up -d --force-recreate или удалить и запустить заново.
Чем docker logs отличается от docker compose logs?
Функционально почти ничем — те же флаги --tail, --follow, --since. Разница в том, что docker compose logs работает с именами сервисов из compose-файла и может показать логи сразу нескольких сервисов проекта одной командой.
Можно ли полностью отключить логирование контейнера?
Да, указав driver: none в опциях logging, но тогда docker logs перестанет что-либо показывать вообще. Обычно разумнее ограничить размер, а не убирать логи целиком — они нужны для отладки.
Нужно ли ставить Loki или ELK сразу, с первого контейнера?
Нет, это оправдано только при нескольких сервисах и серверах. Для одного-двух контейнеров хватает docker logs с флагами и настроенной ротации через max-size/max-file.