Что происходит при docker run: жизненный цикл контейнера
Разбираем по шагам всё, что делает Docker между нажатием Enter на docker run и завершением контейнера.
docker run — это не одна операция, а цепочка: загрузка образа, создание контейнера, запуск процесса PID 1 и, в конце, его остановка и удаление. Контейнер живёт ровно столько, сколько живёт его главный процесс.
Зачем это знать на практике
Самая частая загадка новичка — «я запустил контейнер, а он сразу остановился». Ответ кроется в жизненном цикле: контейнер не «сервер, который работает всегда», а оболочка вокруг одного процесса. Понимание стадий объясняет также, почему приложение нужно учить корректно реагировать на сигналы остановки, чем stop отличается от kill, и какие состояния показывает docker ps.
docker run = create + start (и иногда pull)
Команда docker run — это удобное объединение нескольких шагов. Эквивалентно можно сделать всё по отдельности:
docker pull nginx # 1) загрузить образ, если его нет локально
docker create --name web nginx # 2) создать контейнер (writable-слой, конфиг), но НЕ запускать
docker start web # 3) запустить главный процесс
# то же самое одной командой:
docker run --name web nginx
Разберём стадии:
1. Pull — загрузка образа
Если нужного образа нет в локальном кеше, Docker тянет его из реестра: читает манифест, скачивает недостающие слои (уже имеющиеся переиспользует) и собирает образ из слоёв. Если образ уже есть локально, шаг пропускается.
2. Create — создание контейнера
На этой стадии Docker готовит контейнер, но не запускает процесс: заводит для него writable-слой поверх слоёв образа, формирует конфигурацию (какую команду запускать, переменные окружения, тома, сеть), резервирует имя и ID. Контейнер переходит в состояние created. Ничего ещё не исполняется.
3. Start — запуск PID 1
Здесь runc создаёт namespaces и cgroups (из прошлого урока), монтирует корневую ФС и запускает главный процесс контейнера — команду из CMD/ENTRYPOINT образа или ту, что вы указали. Этот процесс становится PID 1 внутри контейнера, и контейнер переходит в состояние running.
Главный принцип: контейнер живёт, пока жив PID 1
Это ключевая идея всего урока. Контейнер — оболочка вокруг одного главного процесса. Как только PID 1 завершается (неважно, успешно или с ошибкой), контейнер немедленно переходит в состояние exited. Поэтому контейнер, чей процесс отработал и вышел, «умирает» сразу — это не сбой, а ожидаемое поведение.
# PID 1 — это echo: напечатал и вышел → контейнер сразу exited
docker run --name once ubuntu echo "привет"
docker ps -a
# STATUS
# Exited (0) 2 seconds ago
Отсюда типичный казус: docker run ubuntu без команды завершается мгновенно, потому что у образа нет долгоживущего PID 1. Сервер вроде nginx, наоборот, работает в running, потому что его главный процесс не завершается и держит контейнер.
# процесс-сервер не выходит → контейнер остаётся running
docker run -d --name web nginx
docker ps # STATUS: Up 10 seconds
# чтобы «просто ubuntu» жил, дайте ему долгоживущий PID 1
docker run -d --name idle ubuntu sleep 3600
Остановка: SIGTERM, период ожидания, SIGKILL
Когда вы вызываете docker stop, Docker делает graceful shutdown в два этапа. Сначала он шлёт PID 1 сигнал SIGTERM — вежливую просьбу завершиться: процесс может дописать данные, закрыть соединения, сбросить буферы. Затем Docker ждёт grace-период (по умолчанию 10 секунд). Если за это время процесс не вышел, Docker посылает SIGKILL — сигнал, который нельзя перехватить или проигнорировать; ядро убивает процесс немедленно.
docker stop web # SIGTERM, ждать 10s, потом SIGKILL
docker stop -t 30 web # дать приложению 30 секунд на корректное завершение
docker kill web # сразу SIGKILL, без вежливости и ожидания
Разница важна: stop даёт приложению шанс завершиться чисто, kill рубит мгновенно. Чтобы graceful shutdown работал, приложение должно обрабатывать SIGTERM. Если игнорирует — оно всё равно умрёт через grace-период по SIGKILL, но уже грубо, потеряв несохранённое.
Полный цикл состояний
docker create
│
▼
[created] ──docker start──► [running] ──завершился PID 1──► [exited]
│ │ │
│ docker pause docker rm
│ ▼ ▼
│ [paused] (удалён)
└──────────── docker rm ─────────────────────────────────►
Основные состояния: created (создан, не запускался), running (работает), paused (заморожен через cgroup-freezer, docker pause), exited (процесс завершился — код выхода сохранён), а также restarting, если задана политика рестарта. Из exited контейнер можно снова start (writable-слой и конфиг сохранены) либо удалить.
Remove — удаление
Остановленный контейнер не исчезает: он остаётся в состоянии exited, занимая writable-слой и сохраняя логи и код выхода (полезно для разбора). Удаляет его docker rm — вот тогда стирается writable-слой и метаданные. Флаг --rm в docker run удаляет контейнер автоматически сразу после выхода — удобно для разовых запусков.
docker rm web # удалить остановленный контейнер
docker run --rm ubuntu echo hi # запустить и сразу удалить после завершения
docker ps -a --filter status=exited # посмотреть «трупы» контейнеров
Как это работает под капотом
За кулисами dockerd передаёт команды containerd, тот на стадии start поднимает containerd-shim и через runc запускает процесс. Shim остаётся родителем PID 1, ловит его код выхода и сообщает наверх — поэтому даже после рестарта демона код выхода не теряется. Сигналы stop/kill доходят до процесса через ту же цепочку.
Частые ошибки
- Ожидать, что контейнер «работает всегда». Он живёт ровно столько, сколько его PID 1; завершился процесс — завершился контейнер.
- Игнорировать SIGTERM в приложении. Тогда
docker stopкаждый раз ждёт grace-период и добивает по SIGKILL, теряя данные. Обрабатывайте сигнал и завершайтесь сами. - Путать stop и kill.
stop— корректное завершение (SIGTERM → ожидание → SIGKILL),kill— немедленный SIGKILL. - Забывать удалять exited-контейнеры. Они копятся и занимают место; для разовых запусков используйте
--rm.
Итоги
docker run= (при необходимости) pull + create + start; каждую стадию можно выполнить отдельной командой.- На стадии start запускается главный процесс — PID 1; контейнер живёт ровно столько, сколько живёт этот процесс.
docker stopделает graceful shutdown: SIGTERM, grace-период, затем SIGKILL;docker killбьёт SIGKILL сразу.- Состояния: created → running (→ paused) → exited;
docker rmокончательно удаляет контейнер и его writable-слой.