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