Логи Docker-контейнера читаются командой docker logs <имя_или_id>. Физически они лежат на хосте в файле /var/lib/docker/containers/<полный-id>/<полный-id>-json.log. Внутрь контейнера лезть не нужно: демон перехватывает стандартный вывод процесса и пишет его на диск сам.
Первую часть все знают. Вторая - причина, по которой на сервере однажды кончается место, а docker system df показывает, что всё в порядке. Разберём обе. Если контейнеров много и ими управляет оркестратор, логика та же, но появляются свои сущности - основные понятия Kubernetes: Pod, Deployment, Service мы разбирали отдельно.
Docker забирает то, что процесс с PID 1 внутри контейнера пишет в стандартный вывод и поток ошибок. Всё остальное для него не существует.
Если приложение пишет логи в файл внутри контейнера - /app/logs/app.log - демон о них не узнает. Файл будет расти в записываемом слое контейнера, docker logs останется пустым, а при пересоздании контейнера логи исчезнут вместе с ним. Отсюда правило контейнерных образов: приложение пишет в stdout и stderr, а куда это дальше денется - решает не приложение.
Формат файла - по строке JSON на каждую строку вывода:
{"log":"listening on :8080\n","stream":"stdout","time":"2026-08-13T09:14:02.118Z"}
Поле stream разделяет stdout и stderr, и это разделение доживает до команды. docker logs отдаёт вывод контейнера в свой stdout, а ошибки - в свой stderr, поэтому вот такой фильтр работает не так, как ожидают:
docker logs myapp 2>/dev/null | grep ERROR # отбросит как раз stderr, где ошибки и лежат
docker logs myapp 2>&1 | grep -i error # правильно: сначала слить потоки
Базовый набор закрывает почти всё.
docker logs myapp # весь лог с начала жизни контейнера
docker logs -f --tail 100 myapp # последние 100 строк и дальше в реальном времени
docker logs --since 15m myapp # только за последние 15 минут
docker logs --since 2026-08-13T09:00:00 --until 2026-08-13T10:00:00 myapp
docker logs -t myapp # с отметками времени
--tail без -f полезнее, чем кажется: на контейнере, который живёт полгода, простой docker logs попытается прочитать и отдать весь файл целиком. На гигабайтном логе это заметная пауза и лишняя нагрузка на диск.
Для Compose всё то же самое, только по имени сервиса:
docker compose logs -f --tail 50 backend
docker compose logs --since 10m # сразу по всем сервисам проекта
Точный путь к файлу лога конкретного контейнера отдаёт inspect:
docker inspect --format='{{.LogPath}}' myapp
Здесь начинается то, ради чего эта статья.
Драйвер логирования по умолчанию - json-file. У него три опции: max-size, max-file и compress. Значения по умолчанию - -1, 1 и false. То есть ротации нет вообще: файл растёт, пока на разделе есть место. Приложение, которое пишет пару строк на каждый HTTP-запрос, за несколько месяцев спокойно наберёт десятки гигабайт.
Дальше срабатывает вторая ловушка. docker system df считает образы, контейнеры, тома и кеш сборки - логи в этот расчёт не входят. Картина получается такая: df -h говорит, что раздел заполнен на 98 %, docker system df показывает пару гигабайт, и админ ищет проблему не там. Ошибка известная, в трекере moby она висит с 2017 года.
Частая ошибка. Считать, что раз логи не видны в docker system df, их и на диске нет. Проверять надо напрямую: sudo sh -c 'du -sh /var/lib/docker/containers/*/*-json.log' | sort -h | tail -10 - команда покажет десять самых жирных логов и сразу назовёт виновника по id контейнера. Обратите внимание на sh -c: без него звёздочки раскрывает ваша оболочка, а она в каталог /var/lib/docker заглянуть не может, и команда молча вернёт «нет такого файла».
Отдельная неприятность в том, что /var/lib/docker обычно лежит на корневом разделе. Забился он - встал не один контейнер, а весь узел: демон не может создать новый контейнер, systemd не пишет свои журналы, при неудачном стечении обстоятельств не логинится даже ssh. Поэтому на серверах, где контейнеров много, /var/lib/docker разумно выносить на отдельный раздел или диск - тогда переполнение логами останется локальной проблемой Docker, а не аварией всей машины.
Правильное место для настройки - файл /etc/docker/daemon.json на хосте. Он задаёт поведение по умолчанию для всех новых контейнеров:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"compress": "true"
}
}
Такая конфигурация ограничивает каждый контейнер тридцатью мегабайтами: три файла по десять, старые сжимаются. После правки демон надо перезапустить:
sudo systemctl restart docker
Перезапуск демона по умолчанию гасит все работающие контейнеры - это не «мягкая» операция, и на боевом сервере под неё нужно окно. Исключение одно: если в том же daemon.json включён "live-restore": true, демон отцепится от контейнеров, они продолжат работать под containerd, а после старта демон подхватит их обратно. По умолчанию эта опция выключена.
И вот тут два подвоха, на которых спотыкаются чаще всего.
Перезапуск демона не трогает уже созданные контейнеры. Они сохраняют те опции логирования, с которыми были созданы. Новые настройки получат только контейнеры, созданные после перезапуска. Чтобы применить ротацию к существующим, их надо пересоздать: docker compose up -d --force-recreate или руками docker rm и заново docker run. Перезапуск через docker restart не поможет - это тот же самый контейнер.
Проверить, что реально применилось, можно только у конкретного контейнера:
docker inspect --format='{{.HostConfig.LogConfig}}' myapp
Если нужна ротация только для отдельного сервиса, а не для всего хоста, опции задаются при запуске:
docker run --log-opt max-size=10m --log-opt max-file=3 myimage
В Compose то же самое пишется в описании сервиса:
services:
backend:
image: myimage
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
json-file - не единственный вариант, и для части задач он худший из доступных.
| Драйвер | Ротация по умолчанию | Работает docker logs | Когда брать |
|---|---|---|---|
json-file | нет | да | Значение по умолчанию. Годится, если ротацию задали руками |
local | да: 20 МБ × 5 файлов, со сжатием | да | Лучший выбор для одиночного хоста: место под контролем из коробки, формат компактнее |
journald | по правилам systemd-journald | да | Когда логи контейнеров хочется читать теми же journalctl, что и логи системы |
syslog | по правилам приёмника | нет (только кеш) | Отправка на внешний syslog-сервер |
fluentd | на стороне приёмника | нет (только кеш) | Централизованный сбор в стек с обработкой |
none | - | нет | Контейнеры, которые пишут много и бесполезно |
Про docker logs в этой таблице важно: в Docker Community команда работает только с local, json-file и journald. Для удалённых драйверов начиная с версии 20.10 есть двойное логирование - локальный кеш, из которого отдаются последние записи, - но полагаться на него как на архив нельзя.
Драйвер local заслуживает отдельного слова. Он делает ровно то, что от json-file ждут по умолчанию: ограничивает 20 мегабайтами на файл, держит пять файлов, сжимает старые. Сотня мегабайт на контейнер - предсказуемо. Единственная плата - формат бинарный, руками cat его не почитать, только через docker logs.
Опция, о которой вспоминают редко, - mode. По умолчанию она равна blocking: если драйвер не успевает принять строку, процесс в контейнере ждёт. На нагруженном сервисе с медленным диском или подтормаживающим удалённым приёмником это превращается в загадочные задержки ответа, которые не видно ни в одной метрике приложения.
Альтернатива - неблокирующий режим с буфером в памяти:
{
"log-driver": "local",
"log-opts": {
"mode": "non-blocking",
"max-buffer-size": "4m"
}
}
Цена честная: при переполнении буфера строки теряются молча. Для журнала отладки это приемлемо, для аудита - нет.
У логов с дисковой подсистемой отношения вообще более близкие, чем принято думать. Логи пишутся синхронно и мелкими порциями, конкурируя с приложением за одни и те же операции ввода-вывода. На SATA-дисках под нагруженным контейнером это ощутимо, на NVMe - почти нет. Если контейнерная нагрузка живёт на общем хранилище вместе с базами, стоит хотя бы развести их по разным устройствам: сервер под виртуализацию с отдельным диском под /var/lib/docker избавляет от целого класса плавающих проблем.

Файл на хосте - это нормально ровно до тех пор, пока хост один. Дальше начинаются знакомые симптомы: контейнер пересоздали - логи ушли вместе с ним, узел вывели из кластера - расследовать нечего, инцидент затронул четыре сервиса - и вы открываете четыре терминала. Логика тут та же, что и с обычными системными журналами, для чего они нужны и что в них искать мы разбирали отдельно, - только эфемерность контейнеров делает вопрос острее.
Централизованный сбор решает это, но требует своих ресурсов. Честные цифры перед решением: приёмник логов сам по себе - это сервис с диском, индексами и памятью, и на объёмах, которые генерирует десяток болтливых контейнеров, он попросит больше железа, чем сами эти контейнеры. Планируйте хранилище под него отдельно и с запасом, а политику хранения задавайте сразу: недавние логи на быстром диске, архив - на медленном и дешёвом.
Практический порядок действий такой. Сначала научите приложения писать структурированно, в JSON, с одинаковым набором полей. Потом добавьте идентификатор запроса, который проходит через все сервисы - без него в микросервисной архитектуре собрать историю одного обращения невозможно, и это отдельная тема, которую мы разбирали в статье про распределённую трассировку. И только потом ставьте сборщик. В обратном порядке получится дорогое хранилище неструктурированного текста.
Удалять файл лога у работающего контейнера. rm на -json.log место не освободит: демон держит файл открытым, и ядро освободит блоки только когда дескриптор закроется. Диск останется полным, а логи пропадут. Если совсем горит, файл нужно обнулять, а не удалять:
sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' myapp)
Это аварийная мера на один раз. Настоящее решение - ротация, иначе через неделю вернётесь к той же команде.
Навешивать logrotate поверх драйвера. Системный logrotate переименует файл, а Docker продолжит писать в старый дескриптор. Получите растущий файл без имени и пустой docker logs. Ротацией контейнерных логов занимается драйвер, и только он.
Писать в stdout всё подряд. Уровень debug в промышленной среде - самый быстрый способ превратить логи в проблему хранения. Прежде чем настраивать ротацию, стоит посмотреть, что вообще пишется: часто половина объёма - это healthcheck-запросы балансировщика, которые никому не нужны.
| Симптом | Причина | Что делать |
|---|---|---|
df -h показывает полный диск, docker system df - мало | Логи не входят в расчёт docker system df | sudo sh -c 'du -sh /var/lib/docker/containers/*/*-json.log' | sort -h | tail |
docker logs пусто, но приложение работает | Приложение пишет в файл внутри контейнера, а не в stdout | Проверить конфигурацию логирования приложения; при драйвере none - сменить драйвер |
docker logs пусто и драйвер fluentd или syslog | Команда не поддерживается драйвером | Смотреть в приёмнике; локальный кеш даёт только последние записи |
| Ротация настроена, а файл всё равно растёт | Контейнер создан до правки daemon.json | docker inspect --format='{{.HostConfig.LogConfig}}', затем пересоздать контейнер |
docker logs отдаётся десятки секунд | Файл на гигабайты, читается целиком | --tail, --since; после - настроить ротацию |
| Приложение периодически подвисает на записи | mode: blocking при медленном диске или приёмнике | mode: non-blocking с max-buffer-size, разнести диски |
Где хранятся логи Docker-контейнера?
На хосте, в /var/lib/docker/containers/<полный-id>/<полный-id>-json.log. Точный путь для конкретного контейнера - docker inspect --format='{{.LogPath}}' <имя>.
Как посмотреть логи упавшего контейнера?
Так же, как у работающего: docker logs <имя>. Пока контейнер не удалён командой docker rm, его лог на месте. После удаления файл исчезает вместе с каталогом контейнера, и это главный аргумент за централизованный сбор.
Как ограничить размер логов Docker?
Задать max-size и max-file в /etc/docker/daemon.json, перезапустить демон и пересоздать существующие контейнеры. Либо сразу переключиться на драйвер local, где ограничение стоит по умолчанию.
Чем local отличается от json-file?
Ротацией из коробки (20 МБ × 5 файлов со сжатием) и более компактным бинарным форматом. Минус - файл нельзя прочитать обычными текстовыми утилитами, только через docker logs.
Считает ли docker system df размер логов?
Нет. Он показывает образы, контейнеры, тома и кеш сборки. Логи в этих цифрах не учитываются - именно поэтому расхождение с df -h бывает в десятки гигабайт.
По теме: Как посмотреть логи сервера · Docker vs LXC: сравнение технологий контейнеризации · Мониторинг виртуальных машин и контейнеров
Контейнеры упираются в диск, а не в процессор?
Инженеры ITTELO подберут конфигурацию под контейнерную нагрузку - с отдельным устройством под /var/lib/docker, запасом по объёму и нужным типом накопителей. Соберём и протестируем под задачу перед отгрузкой, с гарантией и поддержкой после продажи. На рынке серверов 11+ лет.
Подбор сервера под нагрузку · +7 (800) 551-80-12 · info@ittelo.ru