docker exec и docker logs
docker exec и docker logs — это две команды, которыми ты будешь дебажить упавший Airflow worker, искать причину почему DAG не запускается, и проверять, какие SQL-запросы прилетают в Postgres. Если ты понимаешь их как следует — ты понимаешь основу daily DE debugging workflow с контейнерами.
В этом уроке: как зайти в работающий контейнер через exec, как смотреть логи с фильтрами, что делать если в контейнере нет shell, и почему exec это не то же самое, что attach.
docker exec — запустить процесс в контейнере
docker exec запускает новый процесс внутри уже работающего контейнера. Самое типичное — открыть shell:
docker exec -it pg bash
Что произошло:
- Daemon нашёл контейнер с именем
pg. - Запустил новый процесс
bashвнутри namespaces этого контейнера. - Прикрепил твой терминал к этому процессу через
-it.
Теперь ты внутри. ps aux покажет процессы контейнера, ls / — его файловую систему.
Флаги:
-i— keep stdin open. Без этого нельзя печатать в bash.-t— выделить tty. Без этого bash будет без prompt’а.-it— почти всегда вместе для интерактивных задач.
Для разового запуска команды без интерактивной сессии флаги не нужны:
docker exec pg ls /var/lib/postgresql/data
docker exec pg psql -U postgres -c 'select count(*) from pg_database;'
Это полезно в скриптах: выполнить команду в контейнере и получить вывод обратно.
Что делать, если нет bash или sh
Не во всех образах есть bash. В минимальных alpine — есть только sh. В distroless — нет ничего (нет shell, нет coreutils).
Стратегия для каждого случая
ubuntu/debian/python:slim: docker exec -it CONTAINER bash.
alpine: docker exec -it CONTAINER sh.
distroless / scratch: нет shell. Варианты:
- nsenter с хоста — зайти в namespaces контейнера хостовыми утилитами:
# Получить PID главного процесса контейнера
PID=$(docker inspect -f '{{.State.Pid}}' <container>)
sudo nsenter --target $PID --mount --uts --ipc --net --pid sh
Это работает только если на хосте есть sh и nsenter. На mac (где хост это macOS) — nsenter нет. Но в VM, в которой крутится контейнер, он есть.
- debug image — запустить параллельный контейнер с shell, который подсоединяется к namespace основного:
docker run --rm -it \
--pid container:<main-container> \
--network container:<main-container> \
--volumes-from <main-container> \
alpine sh
Это lifesaver, когда нужно дебажить distroless образ.
- kubectl debug (если в k8s) — то же самое, но с правильным CLI.
stdin, stdout, stderr: три file descriptors
docker logs — стандартный вывод контейнера
Контейнер пишет в stdout/stderr. Docker daemon собирает этот поток и сохраняет в JSON-файл на хосте (по умолчанию). Доступ через:
docker logs pg
Выводит весь лог с начала жизни контейнера. Если контейнер живёт пять дней — это может быть сотни мегабайт.
Полезные флаги
# Последние 100 строк
docker logs --tail 100 pg
# Только за последние 5 минут
docker logs --since 5m pg
# Только до конкретного времени
docker logs --until 2026-05-15T12:00:00 pg
# Follow — как tail -f
docker logs -f pg
# Timestamp каждой строки
docker logs -t pg
# Комбинировать
docker logs -f --tail 50 --since 10m pg
Очень частый паттерн: «что было в логах за последние 5 минут, и следить дальше»:
docker logs -f --since 5m pg
stderr vs stdout
docker logs показывает оба потока, смешано. Чтобы разделить, нужно перенаправить streams:
docker logs pg > stdout.log 2> stderr.log
Если приложение пишет ошибки в stderr (как должно), stderr.log будет содержать только их.
Это полезно, например, для разделения SQL-запросов (stdout) от ошибок (stderr).
Логи для остановленного контейнера
docker logs работает и для остановленных контейнеров — пока контейнер не удалён, лог доступен:
docker logs my-failed-job
Если контейнер удалён (docker rm) — лог тоже удалён вместе с ним.
Это причина, по которой при дебаге не стоит сразу удалять упавший контейнер. Сначала смотришь логи, потом rm.
Где физически лежит лог
На Linux logs лежат в файле:
/var/lib/docker/containers/<container-id>/<container-id>-json.log
Это JSON-файл, каждая строка — один лог-event:
{"log":"started\n","stream":"stdout","time":"2026-05-15T12:34:56.789Z"}
Docker может ротировать этот файл, но по умолчанию — нет. Если контейнер пишет много и долго, файл может вырасти до гигабайтов. В production стандарт — настраивать ротацию или использовать драйвер для centralized logging (syslog, fluentd, journald). Это тема модуля 14.
docker attach vs docker exec — важная разница
Часто junior’ы путают exec и attach. Они делают разное:
Конкретный пример. Запускаем nginx:
docker run -d --name webby nginx
docker exec -it webby bash
# в bash. nginx продолжает работать. exit — webby продолжает работать
docker attach webby
# подключился к stdio nginx (PID 1). видишь access logs.
# Ctrl+C — убил nginx, контейнер остановился
attach редко нужен в DE-сценариях. Используется в основном для:
- Прямой работы с REPL, запущенным в
docker run -it(если случайно отключился). - Когда контейнер был запущен с явным интерактивным процессом и тебе нужно к нему обратно.
99% времени используется exec. Если ты хочешь «зайти в контейнер» — это exec -it CONTAINER bash.
Если ты сделал docker attach, и хочешь выйти не убивая контейнер — нажми последовательно Ctrl+P, Ctrl+Q (detach sequence). Просто Ctrl+C — убьёт главный процесс и контейнер остановится.
Попробуй сам
# 1. Запусти Postgres
docker run -d --name pg -e POSTGRES_PASSWORD=s postgres:16.4
sleep 3
# 2. Зайди внутрь через exec
docker exec -it pg bash
# в bash:
ps aux # увидишь postgres + bash + ps
psql -U postgres -c 'select 1;'
exit # bash вышел, pg продолжает работать
# 3. Посмотри логи
docker logs pg | tail -20
docker logs -f --tail 5 pg & # tail в фоне
sleep 2; kill %1 # убей tail
# 4. Запусти одноразовую команду через exec
docker exec pg date
docker exec pg env | grep POSTGRES
# 5. Прибраться
docker rm -f pg
Связь с дальнейшими модулями
exec и logs — это базовые инструменты для всех последующих debug-сценариев:
- Модуль 12 (Postgres, MinIO, Redis):
docker exec -it pg psql— стандартный способ зайти в локальную Postgres. - Модуль 13 (debug): тут раскроем глубокий debugging через
exec,logs,events,stats. - Модуль 16 (Airflow стенд):
docker logs airflow-worker --since 1m -fбудешь набирать каждый раз, когда DAG не запускается.
Привыкай. После двух недель набора эти команды становятся рефлексом.