Learning Platform
Глоссарий Troubleshooting
Урок 15.01 · 22 мин
Начальный
dockerlogsdebug

docker logs и log drivers

Первое, что делает любой Junior DE, когда контейнер «не работает» — открывает логи. Команда docker logs — это окно в то, что писал процесс PID 1 в stdout/stderr с момента запуска контейнера. Если знаешь как ей пользоваться (фильтры по времени, follow, tail), debug ускоряется в разы.

Параллельно есть менее очевидная тема — log drivers и их настройка. Без max-size в дефолтной конфигурации диск утечёт за неделю — это реальная проблема, которая ломает прод чаще, чем кажется.


journalctl: единый журнал systemd

docker logs базово

Простейший вызов:

docker logs pg

Выведет всё, что контейнер pg писал в stdout и stderr с момента запуска. Если контейнер крутится неделю и много логирует — это могут быть гигабайты, и они хлынут в твой терминал. Поэтому почти всегда нужны флаги.

Основные:

  • --tail N — последние N строк. docker logs pg --tail 100.
  • --follow (-f) — стримит логи в реальном времени, как tail -f. Прервать Ctrl+C.
  • --since DURATION_OR_TIMESTAMP — с указанного момента. --since 5m (последние 5 минут), --since 2026-05-15T10:00:00.
  • --until DURATION_OR_TIMESTAMP — до момента. Удобно для «между 10:00 и 10:30».
  • --timestamps (-t) — добавить ISO-таймстампы к каждой строке.

Самый частый комбо:

docker logs pg --tail 100 --follow --timestamps

«Покажи последние 100 строк и продолжай стримить новые, с таймстампами». Используется в 80% случаев debug’а.

Пример вывода:

2026-05-15T09:42:11.234Z LOG:  database system is ready to accept connections
2026-05-15T09:42:12.567Z LOG:  checkpoint starting: time
2026-05-15T09:42:14.890Z LOG:  checkpoint complete: wrote 12 buffers (0.1%)
2026-05-15T09:42:30.123Z STATEMENT:  SELECT * FROM users WHERE id = 1

Логика времени

docker logs --since 5m берёт текущее время хоста минус 5 минут. Если ты дебажишь упавший в 10:32 контейнер в 11:00 — --since 30m даст логи с 10:30, что покрывает падение.

--since и --until можно сочетать:

docker logs pg --since 2026-05-15T10:00:00 --until 2026-05-15T10:30:00

Логи между 10:00 и 10:30. Это полезно, когда инцидент уже разобран, и нужно дописать post-mortem с конкретными строками.

TIP

Если контейнер остановлен/упал, docker logs всё равно работает (пока контейнер не удалён через docker rm). Это критично для debug crash’а — нашёл контейнер в Exited, посмотрел logs --tail 100, понял причину.


Куда контейнер должен писать логи

Docker перехватывает только stdout и stderr процесса PID 1. Это значит:

  • Если приложение пишет в файл /var/log/app.log внутри контейнера — docker logs его не увидит. Файл живёт в файловой системе контейнера, доступен через docker exec -it cat /var/log/app.log, но не через стандартный механизм логирования.
  • Если приложение пишет в syslog внутри контейнера — тоже не увидит.
  • Если приложение пишет в stdout/stderr — видит.

Best practice для контейнеризированных приложений — писать в stdout/stderr. Это позволяет Docker (и Kubernetes, и любому orchestrator) централизованно собирать логи. Большинство современных фреймворков (Python logging, log4j, structlog) это умеют. Postgres из коробки пишет в stderr контейнера.

Где живут логи контейнера
App PID 1Главный процесс контейнера — единственный, чьи stdout/stderr Docker подхватывает автоматически
stdout
log driverПо умолчанию json-file driver: пишет в /var/lib/docker/containers/<id>/<id>-json.log на хосте
docker logs CLIЧитает файл лога через Docker API и выводит на терминал
JSON-файл на дискеЕсли driver = json-file. Для других driver'ов docker logs может не работать (например, syslog/fluentd)

Log drivers

«Куда Docker сохраняет логи» определяется log driver’ом. На демоне их несколько:

  • json-file (по умолчанию) — JSON-структура с timestamp и stream (stdout/stderr) в файле на хосте. docker logs работает только с этим driver’ом нативно.
  • journald — отправляет в systemd journal. На Linux-серверах с systemd это родная интеграция; journalctl CONTAINER_NAME=pg выдаст логи.
  • syslog — на классический syslog-демон (rsyslog, syslog-ng). Удобно, если у тебя уже есть centralized syslog-сервер.
  • fluentd — на Fluentd-агент, дальше в Elasticsearch / S3 / куда угодно. Стандарт для крупных систем.
  • gelf — Graylog Extended Log Format, для Graylog.
  • awslogs — AWS CloudWatch Logs.
  • none — выключить логирование вообще. Полезно для контейнеров, которые сами знают, куда писать (например, app пишет в Logstash напрямую).

Текущий driver:

docker info | grep "Logging Driver"
# Logging Driver: json-file

Для конкретного контейнера:

docker inspect pg --format '{{.HostConfig.LogConfig.Type}}'
# json-file

Сменить driver глобально — в /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

После правки — sudo systemctl restart docker.


КРИТИЧНО: ограничение размера логов

По умолчанию json-file driver не ограничивает размер логов. Это значит: запускаешь Postgres, он логирует каждый запрос (если включён log_statement = 'all' или slow queries), за неделю накапливается 50 ГБ, диск кончается, сервер падает.

Всегда ставь max-size и max-file:

docker run -d \
  --name pg \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  postgres:16

max-size=10m — каждый файл лога не больше 10 МБ.
max-file=3 — храним максимум 3 файла (rotated). Всего на контейнер не больше 30 МБ.

Когда лог достигает 10 МБ, Docker rotate’ит: текущий становится .1, был .1 становится .2, был .2 удаляется (если уже 3 файла).

В compose:

services:
  postgres:
    image: postgres:16
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

И обязательно ставь это в дефолтном daemon.json — чтобы новые контейнеры по умолчанию получали лимит:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
DANGER

Без max-size лог растёт неограниченно. На production-сервере с десятком контейнеров диск кончится за дни. Это одна из топ-3 причин downtime у команд, которые впервые переезжают на Docker. Поставь это правило сейчас, не «потом разберусь».


compose-level logging

В compose logging: можно задать для всех сервисов сразу через extension:

x-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

services:
  postgres:
    image: postgres:16
    logging: *default-logging

  app:
    image: my-etl
    logging: *default-logging

  redis:
    image: redis:7-alpine
    logging: *default-logging

x-logging — YAML-якорь, *default-logging подставляет его в каждый сервис. Это DRY-паттерн, который встретится в любом серьёзном compose-файле.


Альтернатива: централизованное логирование

В production команды часто не пользуются docker logs на каждом сервере, а отправляют логи в centralized stack: Loki + Grafana, ELK (Elasticsearch + Logstash + Kibana), или SaaS типа Datadog / Honeycomb.

Минимальная схема с Loki:

services:
  app:
    image: my-etl
    logging:
      driver: loki
      options:
        loki-url: "http://loki:3100/loki/api/v1/push"

Тогда docker logs показывает локально, но всё равно идёт в Loki, и можно искать через Grafana по всем сервисам сразу.

Log drivers и куда они отправляют
json-fileДефолт. /var/lib/docker/containers/.../json.log. docker logs работает
вариант
journaldsystemd journal на хосте. journalctl CONTAINER_NAME=pg. Удобно если уже используешь journal
fluentd / lokiPush логов в centralized aggregator. docker logs может не работать локально (зависит от настроек)
ELK / GrafanaПоиск по всем сервисам, дашборды, алёрты по паттернам. Production-grade

Несколько практичных однострочников

# Грепнуть ошибки в логах postgres
docker logs pg 2>&1 | grep -i error

# Стрим логов всех сервисов compose
docker compose logs -f

# Логи конкретного сервиса compose
docker compose logs -f --tail 100 postgres

# Размер лог-файла на хосте (Linux, root)
sudo du -sh /var/lib/docker/containers/$(docker inspect pg --format '{{.Id}}')/

# Очистить лог-файл (НЕ удаляй сам файл — Docker не пересоздаст. truncate)
sudo truncate -s 0 /var/lib/docker/containers/$(docker inspect pg --format '{{.Id}}')/*-json.log

docker compose logs -f особенно полезен — стримит логи всех сервисов сразу, цветовая раскраска по сервису. Когда поднял стенд с Airflow + Postgres + Kafka и что-то не работает — открываешь compose logs -f, видишь, кто и когда что написал.


Попробуй сам

  1. Запусти Postgres с лимитом логов:
    docker run -d --name lab-pg \
      --log-opt max-size=1m \
      --log-opt max-file=2 \
      -e POSTGRES_PASSWORD=lab \
      postgres:16
  2. Посмотри логи: docker logs lab-pg --tail 20 --timestamps.
  3. Сделай тысячу подключений: for i in {1..1000}; do docker exec lab-pg psql -U postgres -c "SELECT $i" > /dev/null; done. Это сгенерит логи коннектов (если включить log_connections=on, см. п. 5).
  4. Стрими логи: docker logs lab-pg -f --since 1m. Прерви через 30 секунд.
  5. Включи log_connections: docker exec lab-pg psql -U postgres -c "ALTER SYSTEM SET log_connections = 'on';", docker restart lab-pg. Снова сделай тысячу коннектов, проверь, что в логах появились строки LOG: connection received.
  6. Бонус: на Linux зайди в /var/lib/docker/containers/<id>/ (через sudo) и посмотри, как выглядит json-file внутри.

Проверка знанийKnowledge check
Зачем обязательно ставить --log-opt max-size и max-file для всех контейнеров (или глобально в /etc/docker/daemon.json), и что произойдёт если этого не сделать на production-сервере?
ОтветAnswer
По умолчанию log driver json-file НЕ ограничивает размер лог-файла. Каждая строка stdout/stderr процесса PID 1 пишется в /var/lib/docker/containers/<id>/<id>-json.log на хосте, и этот файл растёт бесконечно. На production-сервере с 10 контейнерами, которые активно логируют (например, Postgres с log_statement=all, или приложение на каждый HTTP-запрос), за неделю можно набрать 100+ ГБ только лог-файлов. Когда диск кончается: - Docker daemon не может писать новые логи и контейнеры могут падать. - Хост-система перестаёт работать (нет места для tmp, для swap, для пакетов). - Sysadmin приходит чинить, тратит часы на поиск виновника. Решение: max-size=10m max-file=3 (всего 30 МБ на контейнер, при достижении лимита текущий файл rotate'ится, старый удаляется). Глобально в /etc/docker/daemon.json — чтобы новые контейнеры по умолчанию получали лимит. Это правило ставится один раз и навсегда — buy сразу, не "потом разберусь".

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. docker logs показывает только то, что приложение писало в stdout/stderr процесса PID 1. Что произойдёт если приложение пишет в файл /var/log/app.log внутри контейнера?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5