Главная/Подготовка среды/Урок

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 — это не одно действие, а последовательность из пяти.

text
docker run hello-world
        │
        ├─ 1. Есть ли образ hello-world:latest локально?
        │      нет → pull из registry
        │
        ├─ 2. Создать container из образа
        │      (writable layer, network namespace, конфигурация)
        │
        ├─ 3. Запустить главный процесс container
        │
        ├─ 4. Подключить stdout/stderr процесса к терминалу
        │
        └─ 5. Дождаться завершения, вернуть exit code

Эквивалент через отдельные команды:

bash
docker pull -q hello-world
CID="$(docker create hello-world)"   # печатает ID созданного container
docker start -a "$CID"               # -a = attach, подключиться к потокам
docker rm "$CID" > /dev/null
text
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.


Внутренний механизм

Полная последовательность при первом запуске

  1. CLI формирует HTTP-запрос POST /containers/create и отправляет его daemon через /var/run/docker.sock.
  2. Daemon проверяет локальный кэш образов. Образа нет.
  3. Daemon обращается к registry (registry-1.docker.io), получает токен для анонимного доступа.
  4. Daemon скачивает manifest — небольшой JSON-документ, описывающий, из каких слоёв состоит образ и какая конфигурация к нему прилагается.
  5. Daemon скачивает слои. Для hello-world слой один.
  6. Слои распаковываются в каталог данных, вычисляются и проверяются их хэши.
  7. Daemon создаёт container: новый writable layer поверх слоёв образа, набор namespaces, cgroup, сетевой интерфейс.
  8. Daemon через containerd просит runc запустить процесс в подготовленном окружении.
  9. Процесс печатает текст в stdout. Поток передаётся daemon, затем CLI, затем на терминал.
  10. Процесс завершается с кодом 0. Container переходит в состояние exited.

При повторном запуске шаги 3–6 пропускаются: образ уже в кэше.

Что остаётся в системе после запуска

Два объекта:

  • образ hello-world:latest в локальном кэше;
  • container в состоянии exited — он не удаляется автоматически.

Второй пункт — источник накопления мусора. Каждый docker run без флага --rm оставляет остановленный container. Через месяц их могут быть сотни.


Команды и примеры

Первый запуск

bash
sudo docker run hello-world

Ожидаемый результат при первом запуске:

text
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-worldlibrary — namespace официальных образов Docker Hub
17eec7bbc9d7: Pull completeИдентификатор слоя и статус загрузки. У больших образов таких строк много
Digest: sha256:...Неизменяемый идентификатор содержимого образа. Подробно — в разделе 03
Status: Downloaded newer imageОбраз загружен и распакован

Проверка exit code:

bash
echo $?
text
0

Ноль означает, что процесс внутри container завершился успешно. Exit code container становится exit code команды docker run.

Повторный запуск

bash
sudo docker run hello-world

Теперь блок загрузки отсутствует — сразу выводится текст. Образ взят из локального кэша.

Осмотр containers

bash
sudo docker ps
text
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Пусто. docker ps без флагов показывает только работающие containers, а наши уже завершились.

bash
sudo docker ps -a
text
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 — экземпляр.

Осмотр образов

bash
sudo docker images

Вывод в Docker Engine 29 с containerd image store представлен деревом:

text
REPOSITORY    TAG       IMAGE ID       DISK USAGE   CONTENT SIZE   USED
hello-world   latest    f1f77a0f96b7   20.5kB       9.24kB         2 hours ago

На более ранних версиях или при классическом хранилище вывод отличается:

text
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
hello-world   latest    f1f77a0f96b7   3 months ago   20.4kB

Разница между DISK USAGE и CONTENT SIZE объясняется тем, что containerd хранит слои и в сжатом, и в распакованном виде. Подробно — в разделе 03.

Проверка версий

bash
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) не смогут подключиться.

Извлечь конкретное значение:

bash
sudo docker version --format '{{.Server.Version}}'
text
29.0.1

Синтаксис --format использует шаблоны Go. Он встречается по всему курсу и подробно разбирается в разделе 13.

Информация о системе

bash
sudo docker info

Вывод длинный. Ключевые поля для курса:

text
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 Driveroverlayfs (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Где лежат данные

Извлечение отдельных полей:

bash
sudo docker info --format 'Storage: {{.Driver}}  Cgroup: v{{.CgroupVersion}}  Root: {{.DockerRootDir}}'
text
Storage: overlayfs  Cgroup: v2  Root: /var/lib/docker

Если в конце вывода docker info есть блок WARNING: — прочитайте его. Обычно это сообщения о недоступных cgroup-контроллерах, что ограничит возможности из раздела 11.

Запуск container с собственной командой

bash
sudo docker run --rm alpine echo "Привет из container"
text
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
...
Привет из container

Что произошло: загружен образ alpine (около 8 MB), в нём запущена команда echo, вывод передан на терминал, container завершился и был немедленно удалён благодаря --rm.

Флаг --rm — хорошая привычка для разовых запусков: он избавляет от накопления остановленных containers.

Проверим правило «container живёт столько, сколько живёт процесс»:

bash
sudo docker run --rm alpine sleep 3

Команда занимает три секунды, затем завершается. Container существовал ровно эти три секунды.

А теперь — типичная ситуация, которую новички считают ошибкой:

bash
sudo docker run --rm alpine

Команда завершается мгновенно, ничего не выведя. Почему? Образ alpine задаёт команду по умолчанию /bin/sh. Оболочка запускается, обнаруживает, что stdin не подключён и не является терминалом, читает конец файла и завершается. Container вместе с ней.

Чтобы получить интерактивную оболочку, нужны два флага:

bash
sudo docker run --rm -it alpine
text
/ # 

-i подключает stdin, -t выделяет псевдотерминал. Выход — команда exit или Ctrl+D. Подробно эти флаги разбираются в разделе 04.

Использование дискового пространства

bash
sudo docker system df
text
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

Подробнее:

bash
sudo docker system df -v

Показывает построчно каждый образ, container и volume.


Практический пример

Задача: собрать отчёт о состоянии Docker после установки — версии, ключевые параметры, использование диска.

text
docker-check/
└── docker-report.sh
bash
#!/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

Запуск:

bash
chmod +x docker-report.sh
sudo ./docker-report.sh

Проверка результата

Отчёт должен выводиться без ошибок. Проверьте три вещи:

  1. Cgroup version равно v2 (для современной Ubuntu).
  2. Storage driveroverlayfs или overlay2.
  3. Раздел предупреждений отсутствует либо вы понимаете каждое предупреждение.

Очистка

Уберём созданное в этом уроке:

bash
# удалить все остановленные 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 не запускает команду при пустом вводе.

Проверка:

bash
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, затем образ

Контрольные вопросы

На понимание:

  1. Почему docker run при повторном запуске не выводит строки о загрузке?
  2. Почему container с hello-world завершается, хотя ошибки не произошло?
  3. Чем образ отличается от container? Приведите аналогию из программирования.
  4. Что означает Exited (0) в выводе docker ps -a и чем оно отличается от Exited (1)?
  5. Почему docker run alpine завершается мгновенно, а docker run -it alpine даёт оболочку?

На применение:

  1. Как запустить команду в container так, чтобы после неё не осталось объектов?
  2. Как одной командой узнать версию daemon без остального вывода?
  3. Как узнать, сколько места занимает build cache?

На диагностику:

  1. docker run завершается с Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Перечислите три возможные причины.
  2. docker images показывает образ размером 1.2 GB, хотя приложение занимает 30 MB. Куда смотреть дальше?

Краткое резюме

  1. docker run = проверка кэша + возможный pull + create + start + attach + ожидание.
  2. Container живёт ровно столько, сколько живёт его главный процесс.
  3. Из одного образа можно создать сколько угодно containers.
  4. Без --rm остановленные containers остаются в системе.
  5. docker ps показывает работающие, docker ps -a — все.
  6. Образ не обязан содержать дистрибутив Linux — только то, что нужно программе.
  7. docker version показывает версии клиента и сервера отдельно; блок Server подтверждает связь с daemon.
  8. docker info — основной источник сведений о конфигурации: storage driver, cgroup version, security options.
  9. --format с шаблонами Go позволяет извлечь конкретное поле; используется по всему курсу.
  10. docker system df показывает использование диска по категориям.

Официальные источники

ИсточникСсылкаЧто подтверждает
docker run referencehttps://docs.docker.com/reference/cli/docker/container/run/Поведение docker run, флаги --rm, -i, -t
docker info referencehttps://docs.docker.com/reference/cli/docker/system/info/Поля вывода docker info
docker version referencehttps://docs.docker.com/reference/cli/docker/version/Структура вывода и шаблоны --format
docker ps referencehttps://docs.docker.com/reference/cli/docker/container/ls/Колонки вывода и фильтры
docker system dfhttps://docs.docker.com/reference/cli/docker/system/df/Категории использования диска
Docker Engine 29 release noteshttps://docs.docker.com/engine/release-notes/29/Изменение вывода docker images, минимальная версия API 1.44
hello-world imagehttps://hub.docker.com/_/hello-worldНазначение и содержимое образа

Навигация

← Предыдущий материал
Вернуться к разделу
Следующий материал → Docker socket и группа docker
Главное оглавление

Markdown на GitHub ↗