1.4. Первый container
Цели
После этого материала вы сможете:
- объяснить по шагам, что происходит при
docker run hello-world; - читать вывод
docker versionи понимать, откуда берётся каждое значение; - находить в выводе
docker infoключевые параметры: storage driver, cgroup version, security options; - использовать
docker ps,docker images,docker system dfдля осмотра состояния; - запустить container с собственной командой и объяснить, почему он завершился;
- удалить созданные объекты и вернуть систему в исходное состояние.
Предварительные знания
- 1.3. Docker daemon — daemon работает;
- понимание exit code процесса.
Команды в этом уроке выполняются с sudo, если вы ещё не добавили пользователя в группу docker. Этот вопрос разбирается в следующем уроке — там же объясняется, почему решение не так очевидно, как кажется.
Ключевые термины
| Термин | Объяснение |
|---|---|
image | Неизменяемый шаблон: набор слоёв файловой системы плюс метаданные о том, как запускать |
container | Запущенный (или остановленный) экземпляр образа со своим writable layer |
registry | Хранилище образов. По умолчанию — Docker Hub |
pull | Загрузка образа из registry в локальный кэш |
tag | Метка версии образа, например 3.13-slim. По умолчанию — latest |
exit code | Код возврата главного процесса container |
Теория
Что делает docker run
Команда docker run — это не одно действие, а последовательность из пяти.
docker run hello-world
│
├─ 1. Есть ли образ hello-world:latest локально?
│ нет → pull из registry
│
├─ 2. Создать container из образа
│ (writable layer, network namespace, конфигурация)
│
├─ 3. Запустить главный процесс container
│
├─ 4. Подключить stdout/stderr процесса к терминалу
│
└─ 5. Дождаться завершения, вернуть exit code
Эквивалент через отдельные команды:
docker pull -q hello-world
CID="$(docker create hello-world)" # печатает ID созданного container
docker start -a "$CID" # -a = attach, подключиться к потокам
docker rm "$CID" > /dev/null
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
Вывод тот же, что у docker run. Идентификатор здесь сохранён в переменную, а не вписан руками: docker create печатает его в стандартный вывод именно для того, чтобы его можно было подставить дальше.
docker run объединяет их для удобства. Понимание того, что это три разных шага, пригодится в разделе 04.
Что такое hello-world
Это официальный образ размером около 20 KB. Внутри — один скомпилированный бинарный файл, который печатает текст и завершается. Никакой операционной системы, оболочки и библиотек в образе нет.
Это хорошая иллюстрация факта, который часто удивляет: образ не обязан содержать дистрибутив Linux. Он содержит ровно то, что нужно программе. hello-world статически слинкован и не требует ничего, кроме ядра.
Почему container завершился
hello-world печатает текст и завершается. Вместе с завершением главного процесса завершается и container — это фундаментальное правило: container живёт ровно столько, сколько живёт его главный процесс.
Отсюда следует ответ на самый частый вопрос новичков: «почему container сразу упал?». Обычно он не упал — он выполнил свою команду и корректно завершился. Проверяется по exit code.
Внутренний механизм
Полная последовательность при первом запуске
- CLI формирует HTTP-запрос
POST /containers/createи отправляет его daemon через/var/run/docker.sock. - Daemon проверяет локальный кэш образов. Образа нет.
- Daemon обращается к registry (
registry-1.docker.io), получает токен для анонимного доступа. - Daemon скачивает manifest — небольшой JSON-документ, описывающий, из каких слоёв состоит образ и какая конфигурация к нему прилагается.
- Daemon скачивает слои. Для
hello-worldслой один. - Слои распаковываются в каталог данных, вычисляются и проверяются их хэши.
- Daemon создаёт container: новый writable layer поверх слоёв образа, набор namespaces, cgroup, сетевой интерфейс.
- Daemon через containerd просит
runcзапустить процесс в подготовленном окружении. - Процесс печатает текст в stdout. Поток передаётся daemon, затем CLI, затем на терминал.
- Процесс завершается с кодом
0. Container переходит в состояниеexited.
При повторном запуске шаги 3–6 пропускаются: образ уже в кэше.
Что остаётся в системе после запуска
Два объекта:
- образ
hello-world:latestв локальном кэше; - container в состоянии
exited— он не удаляется автоматически.
Второй пункт — источник накопления мусора. Каждый docker run без флага --rm оставляет остановленный container. Через месяц их могут быть сотни.
Команды и примеры
Первый запуск
sudo docker run hello-world
Ожидаемый результат при первом запуске:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
Digest: sha256:940c619fbd418f9b2b1b63e25d8861f9cc1b46e3fc8b018ccfe8b78f19b8cc4f
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
...
Разбор служебной части вывода:
| Строка | Что означает |
|---|---|
Unable to find image ... locally | Образа нет в кэше, будет загрузка. Это не ошибка |
latest: Pulling from library/hello-world | library — namespace официальных образов Docker Hub |
17eec7bbc9d7: Pull complete | Идентификатор слоя и статус загрузки. У больших образов таких строк много |
Digest: sha256:... | Неизменяемый идентификатор содержимого образа. Подробно — в разделе 03 |
Status: Downloaded newer image | Образ загружен и распакован |
Проверка exit code:
echo $?
0
Ноль означает, что процесс внутри container завершился успешно. Exit code container становится exit code команды docker run.
Повторный запуск
sudo docker run hello-world
Теперь блок загрузки отсутствует — сразу выводится текст. Образ взят из локального кэша.
Осмотр containers
sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Пусто. docker ps без флагов показывает только работающие containers, а наши уже завершились.
sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a3f2b9c1d4e5 hello-world "/hello" 2 minutes ago Exited (0) 2 minutes ago relaxed_hopper
7b8c9d0e1f2a hello-world "/hello" 5 minutes ago Exited (0) 5 minutes ago nifty_lamarr
Разбор колонок:
| Колонка | Значение |
|---|---|
CONTAINER ID | Сокращённый идентификатор. Полный — 64 шестнадцатеричных символа |
IMAGE | Образ, из которого создан container |
COMMAND | Команда, заданная в образе. Здесь — /hello, тот самый бинарный файл |
STATUS | Состояние и exit code в скобках |
NAMES | Имя. Если не задано через --name, генерируется как «прилагательное_фамилия» |
Обратите внимание: два запуска создали два разных container из одного образа. Образ — шаблон, container — экземпляр.
Осмотр образов
sudo docker images
Вывод в Docker Engine 29 с containerd image store представлен деревом:
REPOSITORY TAG IMAGE ID DISK USAGE CONTENT SIZE USED
hello-world latest f1f77a0f96b7 20.5kB 9.24kB 2 hours ago
На более ранних версиях или при классическом хранилище вывод отличается:
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest f1f77a0f96b7 3 months ago 20.4kB
Разница между DISK USAGE и CONTENT SIZE объясняется тем, что containerd хранит слои и в сжатом, и в распакованном виде. Подробно — в разделе 03.
Проверка версий
sudo docker version
Вывод состоит из двух блоков. Полезные поля:
| Поле | Значение |
|---|---|
Client.Version | Версия CLI |
Client.API version | Версия API, которую использует CLI |
Server.Engine.Version | Версия daemon |
Server.Engine.API version | Версия API daemon и минимальная поддерживаемая |
Server.containerd.Version | Версия containerd |
Server.runc.Version | Версия runc |
Начиная с Docker Engine 29 минимальная поддерживаемая версия API — 1.44. Это означает, что очень старые клиенты (до Docker 25) не смогут подключиться.
Извлечь конкретное значение:
sudo docker version --format '{{.Server.Version}}'
29.0.1
Синтаксис --format использует шаблоны Go. Он встречается по всему курсу и подробно разбирается в разделе 13.
Информация о системе
sudo docker info
Вывод длинный. Ключевые поля для курса:
Server:
Containers: 2
Running: 0
Paused: 0
Stopped: 2
Images: 1
Server Version: 29.0.1
Storage Driver: overlayfs
Logging Driver: local
Cgroup Driver: systemd
Cgroup Version: 2
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Security Options:
apparmor
seccomp
Profile: builtin
Kernel Version: 6.8.0-88-generic
Operating System: Ubuntu 24.04.3 LTS
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 15.3GiB
Docker Root Dir: /var/lib/docker
Live Restore Enabled: true
Что здесь важно:
| Поле | Почему важно |
|---|---|
Storage Driver | overlayfs (containerd store) или overlay2 (классический). Влияет на раздел 03 |
Cgroup Version: 2 | Подтверждает выводы урока 1.1. От этого зависят пути в разделе 17 |
Cgroup Driver: systemd | Кто управляет cgroup. systemd — правильное значение для systemd-систем |
Security Options | Активные механизмы защиты. Разбираются в разделе 12 |
CPUs, Total Memory | Ресурсы, видимые daemon. Влияют на расчёт worker count в разделе 06 |
Docker Root Dir | Где лежат данные |
Извлечение отдельных полей:
sudo docker info --format 'Storage: {{.Driver}} Cgroup: v{{.CgroupVersion}} Root: {{.DockerRootDir}}'
Storage: overlayfs Cgroup: v2 Root: /var/lib/docker
Если в конце вывода docker info есть блок WARNING: — прочитайте его. Обычно это сообщения о недоступных cgroup-контроллерах, что ограничит возможности из раздела 11.
Запуск container с собственной командой
sudo docker run --rm alpine echo "Привет из container"
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
...
Привет из container
Что произошло: загружен образ alpine (около 8 MB), в нём запущена команда echo, вывод передан на терминал, container завершился и был немедленно удалён благодаря --rm.
Флаг --rm — хорошая привычка для разовых запусков: он избавляет от накопления остановленных containers.
Проверим правило «container живёт столько, сколько живёт процесс»:
sudo docker run --rm alpine sleep 3
Команда занимает три секунды, затем завершается. Container существовал ровно эти три секунды.
А теперь — типичная ситуация, которую новички считают ошибкой:
sudo docker run --rm alpine
Команда завершается мгновенно, ничего не выведя. Почему? Образ alpine задаёт команду по умолчанию /bin/sh. Оболочка запускается, обнаруживает, что stdin не подключён и не является терминалом, читает конец файла и завершается. Container вместе с ней.
Чтобы получить интерактивную оболочку, нужны два флага:
sudo docker run --rm -it alpine
/ #
-i подключает stdin, -t выделяет псевдотерминал. Выход — команда exit или Ctrl+D. Подробно эти флаги разбираются в разделе 04.
Использование дискового пространства
sudo docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 2 0 8.32MB 8.32MB (100%)
Containers 2 0 0B 0B
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0B
Подробнее:
sudo docker system df -v
Показывает построчно каждый образ, container и volume.
Практический пример
Задача: собрать отчёт о состоянии Docker после установки — версии, ключевые параметры, использование диска.
docker-check/
└── docker-report.sh
#!/usr/bin/env bash
# docker-report.sh — отчёт о состоянии Docker Engine.
set -euo pipefail
if ! docker info > /dev/null 2>&1; then
echo "Не удалось связаться с Docker daemon." >&2
echo "Проверьте: systemctl status docker" >&2
exit 1
fi
fmt() { printf ' %-22s %s\n' "$1:" "$2"; }
echo "=== Версии ==="
fmt "Docker Engine" "$(docker version --format '{{.Server.Version}}')"
fmt "Docker CLI" "$(docker version --format '{{.Client.Version}}')"
fmt "API (server)" "$(docker version --format '{{.Server.APIVersion}}')"
fmt "API (минимум)" "$(docker version --format '{{.Server.MinAPIVersion}}')"
fmt "containerd" "$(docker version --format '{{range .Server.Components}}{{if eq .Name "containerd"}}{{.Version}}{{end}}{{end}}')"
fmt "runc" "$(docker version --format '{{range .Server.Components}}{{if eq .Name "runc"}}{{.Version}}{{end}}{{end}}')"
fmt "Compose" "$(docker compose version --short 2>/dev/null || echo 'не установлен')"
fmt "Buildx" "$(docker buildx version 2>/dev/null | awk '{print $2}' || echo 'не установлен')"
echo
echo "=== Конфигурация ==="
fmt "Storage driver" "$(docker info --format '{{.Driver}}')"
fmt "Logging driver" "$(docker info --format '{{.LoggingDriver}}')"
fmt "Cgroup driver" "$(docker info --format '{{.CgroupDriver}}')"
fmt "Cgroup version" "v$(docker info --format '{{.CgroupVersion}}')"
fmt "Live restore" "$(docker info --format '{{.LiveRestoreEnabled}}')"
fmt "Data root" "$(docker info --format '{{.DockerRootDir}}')"
echo
echo "=== Ресурсы, видимые daemon ==="
fmt "CPUs" "$(docker info --format '{{.NCPU}}')"
fmt "Memory" "$(docker info --format '{{.MemTotal}}' | awk '{printf "%.1f GiB\n", $1/1073741824}')"
echo
echo "=== Объекты ==="
fmt "Образов" "$(docker images -q | wc -l)"
fmt "Containers всего" "$(docker ps -aq | wc -l)"
fmt "Из них работают" "$(docker ps -q | wc -l)"
fmt "Volumes" "$(docker volume ls -q | wc -l)"
echo
echo "=== Использование диска ==="
docker system df | sed 's/^/ /'
warnings="$(docker info 2>&1 | grep -i '^WARNING' || true)"
if [ -n "$warnings" ]; then
echo
echo "=== Предупреждения ==="
echo "$warnings" | sed 's/^/ /'
fi
Запуск:
chmod +x docker-report.sh
sudo ./docker-report.sh
Проверка результата
Отчёт должен выводиться без ошибок. Проверьте три вещи:
Cgroup versionравноv2(для современной Ubuntu).Storage driver—overlayfsилиoverlay2.- Раздел предупреждений отсутствует либо вы понимаете каждое предупреждение.
Очистка
Уберём созданное в этом уроке:
# удалить все остановленные containers из этого урока
sudo docker ps -a --filter "ancestor=hello-world" -q | xargs -r sudo docker rm
# удалить образы
sudo docker rmi hello-world alpine
Разбор: --filter "ancestor=hello-world" отбирает containers, созданные из указанного образа; -q выводит только идентификаторы; xargs -r не запускает команду при пустом вводе.
Проверка:
sudo docker ps -a
sudo docker images
Оба списка должны быть пустыми (или не содержать объектов этого урока).
Команда
docker system pruneудаляет всё неиспользуемое сразу. Она удобна, но опасна: без внимательного чтения вывода можно удалить нужные данные. Она разбирается отдельно в разделе 13, где показано, как сначала посмотреть, что именно будет удалено.
Типичные ошибки
| Ошибка | Причина | Исправление |
|---|---|---|
«Container сразу упал» для docker run alpine | Главный процесс (/bin/sh) завершился, не получив stdin | Добавить -it для интерактивной работы или задать долгоживущую команду |
Накопление сотен Exited containers | Запуски без --rm | Использовать --rm для разовых запусков; периодически чистить |
docker ps не показывает завершившийся container | Без флагов показываются только работающие | Использовать docker ps -a |
| Путаница образа и container | Образ — шаблон, container — экземпляр; из одного образа их может быть много | Проверять docker images и docker ps -a раздельно |
Игнорирование блока WARNING в docker info | Кажется несущественным | Каждое предупреждение означает недоступную возможность; разобрать до продолжения курса |
docker rmi не удаляет образ | На образ ссылается существующий container | Сначала удалить containers, затем образ |
Контрольные вопросы
На понимание:
- Почему
docker runпри повторном запуске не выводит строки о загрузке? - Почему container с
hello-worldзавершается, хотя ошибки не произошло? - Чем образ отличается от container? Приведите аналогию из программирования.
- Что означает
Exited (0)в выводеdocker ps -aи чем оно отличается отExited (1)? - Почему
docker run alpineзавершается мгновенно, аdocker run -it alpineдаёт оболочку?
На применение:
- Как запустить команду в container так, чтобы после неё не осталось объектов?
- Как одной командой узнать версию daemon без остального вывода?
- Как узнать, сколько места занимает build cache?
На диагностику:
docker runзавершается сCannot connect to the Docker daemon at unix:///var/run/docker.sock. Перечислите три возможные причины.docker imagesпоказывает образ размером 1.2 GB, хотя приложение занимает 30 MB. Куда смотреть дальше?
Краткое резюме
docker run= проверка кэша + возможный pull + create + start + attach + ожидание.- Container живёт ровно столько, сколько живёт его главный процесс.
- Из одного образа можно создать сколько угодно containers.
- Без
--rmостановленные containers остаются в системе. docker psпоказывает работающие,docker ps -a— все.- Образ не обязан содержать дистрибутив Linux — только то, что нужно программе.
docker versionпоказывает версии клиента и сервера отдельно; блокServerподтверждает связь с daemon.docker info— основной источник сведений о конфигурации: storage driver, cgroup version, security options.--formatс шаблонами Go позволяет извлечь конкретное поле; используется по всему курсу.docker system dfпоказывает использование диска по категориям.
Официальные источники
| Источник | Ссылка | Что подтверждает |
|---|---|---|
| docker run reference | https://docs.docker.com/reference/cli/docker/container/run/ | Поведение docker run, флаги --rm, -i, -t |
| docker info reference | https://docs.docker.com/reference/cli/docker/system/info/ | Поля вывода docker info |
| docker version reference | https://docs.docker.com/reference/cli/docker/version/ | Структура вывода и шаблоны --format |
| docker ps reference | https://docs.docker.com/reference/cli/docker/container/ls/ | Колонки вывода и фильтры |
| docker system df | https://docs.docker.com/reference/cli/docker/system/df/ | Категории использования диска |
| Docker Engine 29 release notes | https://docs.docker.com/engine/release-notes/29/ | Изменение вывода docker images, минимальная версия API 1.44 |
| hello-world image | https://hub.docker.com/_/hello-world | Назначение и содержимое образа |
Навигация
← Предыдущий материал
Вернуться к разделу
Следующий материал → Docker socket и группа docker
Главное оглавление