MAATRIX / Блог / Логирование в Docker: лучшие практики

Логирование в Docker: лучшие практики

Логирование в Docker: лучшие практики

MAATRIX

Через несколько месяцев работы контейнеров сервер вдруг сообщает, что диск закончился, хотя вы ничего крупного не заливали и не разворачивали. В девяти случаях из десяти виноваты логи контейнеров: 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: 3 Docker держит текущий файл плюс два архивных, старые удаляются автоматически.

С такими значениями на контейнер уйдёт максимум 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.