Жизненный цикл контейнера и основные команды
Контейнер — это запущенный процесс в изолированном окружении. Он не “маленькая виртуальная машина”: если главный процесс завершился, контейнер тоже завершился.
Перед первой лабораторной важно связать команды с жизненным циклом:
| Состояние | Что происходит | Команды |
|---|---|---|
| image (образ) есть локально | Docker может из него создать контейнер | docker images, docker pull |
| container created/running | контейнер создан или работает | docker run, docker ps |
| container stopped | процесс завершён, файловая система контейнера осталась | docker ps -a, docker start |
| container removed | контейнер удалён | docker rm |
Главная команда: docker run
docker run делает сразу несколько шагов: при необходимости скачивает image, создаёт контейнер, запускает процесс и подключает вывод к терминалу.
В примерах часто встречается запись вида nginx:alpine.
nginx:alpine
│ │
│ └─ tag: вариант образа
└────── repository/image name: имя образа
nginx - имя image, а alpine - tag. Tag уточняет, какую версию или вариант image взять. В этой лаборатории используй nginx:alpine целиком как точное имя образа. Подробно про tags, registry и почему лучше не полагаться на latest будет в следующей статье про images.
docker run nginx:alpine
docker run -d --name web nginx:alpine
docker run --rm busybox:latest echo hello
Разбор docker run -d --name web nginx:alpine: docker run создаёт новый контейнер из image, -d запускает его в фоне, --name web задаёт имя контейнера, nginx:alpine — image с tag. В docker run --rm busybox:latest echo hello после имени image указана команда внутри контейнера: BusyBox запускает echo hello, печатает строку и завершается, а --rm удаляет контейнер после выхода.
Флаги меняют поведение:
| Флаг | Зачем |
|---|---|
-d |
запустить в фоне |
--name |
дать понятное имя |
--rm |
удалить контейнер после завершения |
-p |
пробросить порт |
-v |
подключить volume или директорию |
Не пытайся учить все флаги сразу. Сначала пойми: какой процесс запускается, должен ли контейнер жить долго, нужен ли доступ к порту и нужно ли сохранять данные.
run создаёт новый контейнер
docker run --name web -d nginx:alpine
Эта команда делает несколько действий: при необходимости скачивает image, создаёт новый контейнер, запускает главный процесс и возвращает управление терминалу из-за -d.
Если повторить команду с тем же --name web, Docker выдаст ошибку: контейнер с таким именем уже существует. Чтобы запустить уже существующий контейнер, нужен start:
docker stop web
docker start web
run - новый контейнер. start - существующий контейнер.
Что Docker делает внутри при run
Когда ты вводишь docker run, CLI отправляет запрос Docker daemon. Дальше daemon выполняет цепочку действий:
- Проверяет, есть ли image локально.
- Если image нет, скачивает его из registry.
- Создаёт writable layer — записываемый слой контейнера поверх image.
- Настраивает namespaces, cgroups, network и mounts.
- Передаёт запуск container runtime.
- Runtime запускает главный процесс контейнера.
На этом шаге важно помнить связь с архитектурой Docker: CLI не создаёт namespaces сам, а отправляет запрос в daemon. dockerd подготавливает Docker-часть, containerd управляет жизненным циклом контейнера, а runc делает низкоуровневый запуск процесса через Linux namespaces и cgroups.
Поэтому ошибка docker run может относиться к разным слоям: нет image, нет доступа к registry, занят порт, не существует путь для bind mount, процесс внутри контейнера сразу завершился.
Stop, kill, restart
docker stop web
docker kill web
docker restart web
stop отправляет процессу сигнал завершения и даёт время корректно остановиться. kill завершает принудительно. В обычной работе сначала используй stop, а kill оставляй для зависших контейнеров.
Exec и команда по умолчанию
docker exec web hostname
docker exec -it web sh
exec не создаёт новый контейнер. Он запускает дополнительную команду внутри уже работающего. Для интерактивной shell нужны -it: pseudo-TTY и stdin.
Разбор docker exec -it web sh: docker exec запускает команду в running container, -i держит stdin открытым, -t выделяет терминал, web — имя контейнера, sh — команда внутри контейнера.
Команду по умолчанию можно переопределить при run:
docker run --rm busybox:latest echo hello
Контейнер выполнит echo hello и завершится. Флаг --rm удалит контейнер после завершения, что удобно для одноразовых запусков.
Проверка после docker run: ps, logs, inspect
После docker run не ограничивайся фактом “команда не упала”. В Docker важно проверить, что именно создалось и в каком состоянии оно находится.
docker ps
docker logs web --tail=20
docker inspect web
docker stats --no-stream
Команды отвечают на разные вопросы:
| Команда | Что проверяет |
|---|---|
docker ps |
контейнер сейчас работает или нет |
docker ps -a |
есть ли контейнер в истории, даже если он уже завершился |
docker logs web |
что главный процесс написал в stdout/stderr |
docker inspect web |
полное состояние контейнера в JSON: status, command, image, сеть, mounts |
docker stats --no-stream |
сколько CPU и памяти контейнер использует прямо сейчас |
inspect лучше воспринимать не как первую команду Docker, а как “посмотреть паспорт объекта”. Сначала ты создаёшь контейнер через docker run, потом через inspect проверяешь, что Docker реально сохранил про этот контейнер.
Например:
docker run -d --name web nginx:alpine
docker inspect web
В полном JSON много полей, поэтому часто используют --format:
docker inspect -f '{{.State.Status}}' web
docker inspect -f '{{.Config.Image}}' web
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
Так ты получаешь не весь JSON, а конкретный факт: статус, image или IP-адрес контейнера.
Если контейнер сразу выходит, смотри:
docker ps -a
docker logs <container>
docker inspect -f '{{.State.ExitCode}}' <container>
Exit code контейнера - код завершения главного процесса. Это такой же сигнал успеха/ошибки, как $? в shell.
inspect есть не только у контейнеров. Docker-объекты устроены одинаково: у них есть состояние и metadata, которые можно посмотреть.
docker image inspect nginx:alpine
docker network inspect bridge
docker volume inspect app-data
Но логика та же: сначала пойми, какой объект проверяешь, потом доставай нужное поле.
Лимиты CPU и RAM для контейнера
Контейнер по умолчанию не получает “маленькую отдельную VM” с заранее выделенной памятью. Он запускается как процесс на хосте и использует ресурсы хоста через Linux cgroups. Поэтому в реальной работе контейнерам часто задают лимиты: чтобы один сервис не забрал всю RAM или CPU и не положил соседние контейнеры.
Для первых запусков Docker можно обойтись без лимитов. Но полезно рано увидеть саму идею: контейнер — это обычный процесс с управляемыми границами по ресурсам.
Пример запуска nginx с ограничением памяти и CPU:
docker run -d \
--name web-limited \
--memory 256m \
--cpus 0.5 \
nginx:alpine
Что означают параметры:
| Параметр | Что ограничивает |
|---|---|
--memory 256m |
контейнер может использовать до 256 MB RAM |
--cpus 0.5 |
контейнер может использовать примерно половину одного CPU core |
--cpus 2 |
контейнер может использовать примерно два CPU core |
Проверить текущее потребление можно через:
docker stats web-limited --no-stream
Посмотреть сохранённые лимиты контейнера:
docker inspect -f '{{.HostConfig.Memory}}' web-limited
docker inspect -f '{{.HostConfig.NanoCpus}}' web-limited
HostConfig.Memory выводится в байтах. Например, 268435456 — это 256 MB. NanoCpus хранится в “наноядрах”: 500000000 примерно соответствует --cpus 0.5.
Если контейнер превышает memory limit, ядро может завершить процесс внутри контейнера. В Docker это часто видно как OOMKilled:
docker inspect -f '{{.State.OOMKilled}}' web-limited
Лимиты не делают приложение быстрее. Они задают границы. Если поставить слишком маленькую память, сервис будет падать. Если не поставить лимиты вообще, шумный или сломанный контейнер может мешать всему серверу. Поэтому в production обычно начинают с разумного лимита, смотрят docker stats, метрики приложения и постепенно корректируют значения.
Чтобы логи не съели весь диск
docker logs показывает вывод главного процесса контейнера: всё, что приложение пишет в stdout и stderr. Например, nginx пишет access log, приложение пишет ошибки, worker пишет ход выполнения задач. Docker забирает этот поток и сохраняет его через logging driver.
Это уже эксплуатационная настройка, а не обязательный шаг для каждой учебной команды. Но она часто встречается на реальных серверах, поэтому важно понимать принцип.
По умолчанию на многих Linux-серверах используется driver json-file. Это значит, что Docker хранит логи контейнера в JSON-файлах на диске хоста, обычно внутри /var/lib/docker/containers/....
Проблема в том, что без ограничения размера такой файл может расти очень долго. Если приложение шумное, пишет debug-логи или попало в цикл ошибок, логи способны занять весь диск. После этого начинают ломаться уже не только контейнеры: могут не создаваться новые файлы, падать сервисы и перестать нормально работать сам Docker.
Проверить текущий logging driver можно так:
docker info --format '{{.LoggingDriver}}'
Посмотреть log-файл конкретного контейнера:
docker inspect -f '{{.LogPath}}' web
Обычно на сервере задают ограничение размера логов Docker daemon. Для этого создают или редактируют файл /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Что здесь происходит:
| Поле | Зачем нужно |
|---|---|
log-driver |
говорит Docker, каким способом хранить логи контейнеров |
json-file |
стандартный локальный формат логов, с которым работает docker logs |
max-size |
максимальный размер одного log-файла контейнера |
max-file |
сколько старых файлов Docker оставит при ротации |
При настройке выше один контейнер займёт логами примерно до 30m: три файла по 10m. Когда текущий файл достигает лимита, Docker начинает новый, а самые старые удаляет.
После изменения /etc/docker/daemon.json Docker daemon нужно перезапустить:
sudo systemctl restart docker
Важная деталь: настройка daemon применяется к новым контейнерам. Уже созданные контейнеры лучше пересоздать, чтобы они получили новые log options.
Для отдельного контейнера лимит можно задать прямо в docker run:
docker run -d \
--name web \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
nginx:alpine
Но в реальной работе чаще настраивают daemon глобально, чтобы не вспоминать про log options в каждом запуске. Это не заменяет нормальную систему логирования, например Loki, Elasticsearch или journald, но защищает сервер от простой и частой проблемы: один шумный контейнер забил весь диск.
Удаление
docker rm web
docker rm -f web
docker rm удаляет остановленный контейнер. -f сначала принудительно остановит running container, потом удалит. Не путай удаление контейнера с удалением image: docker rmi работает с образами.
Имена, ID и выбор контейнера
Docker позволяет обращаться к контейнеру по имени или ID:
docker ps
docker logs web
docker logs 4f2a
В лабораторных лучше задавать --name, потому что имя стабильно и команды читаются понятнее. Если имя не задано, Docker сгенерирует случайное.
Автоудаление --rm
--rm хорошо подходит для одноразовых контейнеров:
docker run --rm busybox:latest date
Но если нужно после завершения посмотреть logs, inspect или exit code, --rm мешает: контейнер уже удалён. Для диагностики сначала запускай без --rm, а после проверки удаляй вручную.
Интерактивные контейнеры
docker run -it --name debug alpine sh
-i держит stdin открытым, -t выделяет pseudo-terminal. Без них shell внутри контейнера может сразу завершиться или работать неудобно.
Полезные фильтры
docker ps -a --filter name=web
docker ps -a --filter status=exited
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
--filter name=web оставляет контейнеры, у которых имя совпадает с фильтром. --filter status=exited показывает завершённые контейнеры. --format управляет колонками вывода: {{.Names}}, {{.Status}}, {{.Image}} — поля Docker-объекта, а table добавляет заголовок таблицы.
Фильтры особенно удобны, когда на учебной VM много контейнеров от разных лабораторий.
Foreground и detached режим
Без -d контейнер привязан к текущему терминалу:
docker run nginx:alpine
Ты увидишь логи nginx прямо в терминале. Для выхода часто используют Ctrl+C, и это остановит процесс. С -d контейнер уходит в фон:
docker run -d --name web nginx:alpine
docker logs -f web
Для серверных процессов в лабах почти всегда нужен detached режим, потому что дальше нужно выполнять проверочные команды.
Attach
docker attach web
attach подключается к stdout/stdin главного процесса контейнера. Это не то же самое, что exec. Для диагностики чаще безопаснее logs и exec, потому что attach может перехватить текущую сессию процесса.
Restart policy через Docker CLI
docker run -d --restart unless-stopped --name web nginx:alpine
Restart policy говорит daemon перезапускать контейнер при сбое или рестарте Docker. Но если приложение внутри контейнера падает из-за конфигурации, policy может создать бесконечный цикл перезапусков. Тогда смотри docker logs и docker inspect.
Мини-runbook для упавшего контейнера
docker ps -a
docker logs <container> --tail=100
docker inspect -f '{{.State.ExitCode}} {{.State.Error}}' <container>
docker inspect -f '{{.Config.Cmd}}' <container>
Если exit code ненулевой, причина часто уже есть в logs. Если logs пустые, проверь command/CMD: возможно, процесс завершился штатно или не стартовал из-за неправильной команды.
Не удаляй контейнер до просмотра logs и inspect, если хочешь понять причину. После docker rm диагностический контекст исчезнет.
