Образы Docker: создание, оптимизация и управление ими.

Docker-образ (image)— это статический слепок (шаблон), включающий все необходимые файлы, библиотеки, настройки и инструкции для запуска контейнера. Внутри каждый Docker-образ построен из слоёв (layers). Эти слои делаются из команд, которые надстраиваются поверх базовой основы.

Что такое слои?

  • Слои — это неизменяемые части образа, каждый слой — результат выполнения одной команды Dockerfile (например, установка пакета, копирование файла).
  • Кэширование — Docker использует кэш слоёв. Если слой не изменился, он берётся из кеша, что ускоряет сборку.
  • Многослойная структура — это подобие конструкторов, где каждый слой добавляет что-то новое. Финальный образ — это сумма всех слоёв.

Почему это важно?

  • Можно повторно использовать слои при создании новых образов, что ускоряет процесс.
  • Размер образа можно уменьшить, минимизируя количество и размер слоёв.
  • Образы можно легко обновлять — просто добавляя новые слои поверх старых.

Создаем образ на базе Alpine Linux: пошаговая инструкция

Начинаем с минимального образа Alpine Linux — он очень легкий и быстрый, подходит для обучения и простых приложений. Если вы не знакомы с базовыми командами для работы с контейнерами, вы можете найти ответы в отдельной статье.

Шаг 1. Начальный образ (alpine)

Скачаем образ alpine – это базовый, минимальный образ. Он содержит минимальную установку Linux, с очень ограниченным набором команд и программ. Данный образ послужит нам заготовкой.

Скачиваем образ и проверяем список образов:

[root@waky ~]# docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
2d35ebdb57d9: Pull complete
Digest: sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
[root@waky ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
alpine       latest    706db57fb206   2 weeks ago   8.32MB
[root@waky ~]# 

Образ успешно скачан, теперь он доступен локально.

Создадим из этого образа контейнер и запустим его в фоновом режиме.

[root@waky ~]# docker run -td --name alpine_0 alpine /bin/sh
06611f5fc0e48a03176ec65635c7245ae3b19a79ce8db24ef9c87cb77789e44c
[root@waky ~]# docker ps
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES
06611f5fc0e4   alpine    "/bin/sh"   6 seconds ago   Up 5 seconds             alpine_0
[root@waky ~]#

Зайдем в консоль этого контейнера:

[root@waky ~]# docker exec -it alpine_0 sh
/ #

Мы попали в условно отдельный маленький Linux, можете попробовать исполнить базовые команды (ls, cd и прочие)

Вы можете обнаружить, что нет многих привычных команд и утилит, например такого полезного инструмента как curl.

Давайте установим его:

/ # curl -I https://www.google.com
sh: curl: not found
/ # apk add curl
fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/x86_64/APKINDEX.tar.gz
(1/9) Installing brotli-libs (1.1.0-r2)
(2/9) Installing c-ares (1.34.5-r0)
(3/9) Installing libunistring (1.3-r0)
(4/9) Installing libidn2 (2.3.7-r0)
(5/9) Installing nghttp2-libs (1.65.0-r0)
(6/9) Installing libpsl (0.21.5-r3)
(7/9) Installing zstd-libs (1.5.7-r0)
(8/9) Installing libcurl (8.14.1-r2)
(9/9) Installing curl (8.14.1-r2)
Executing busybox-1.37.0-r19.trigger
OK: 12 MiB in 25 packages
/ # curl -I https://www.google.com
HTTP/2 200
content-type: text/html; charset=ISO-8859-1
…
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
/ #

Мы успешно установили и проверили работу утилиты. Но что будет, если пересоздать контейнер?

Выйдем из консоли контейнера, удалим и создадим его по новой:

/ # exit
[root@waky ~]# docker stop alpine_0
alpine_0
[root@waky ~]# docker rm alpine_0
alpine_0
[root@waky ~]# docker run -td --name alpine_0 alpine /bin/sh
fc92bbca682977a8eb85e2bd84a1bc4238db260eaaf10a3ddaae37b6c8179a22
[root@waky ~]# docker exec -it alpine_0 sh
/ # curl -I https://www.google.com
sh: curl: not found
/ # exit
[root@waky ~]#

Очевидно, в новом контейнере снова отсутствует curl. Контейнер каждый раз создается из базового образа, в котором нет данной программы. Чтобы не устанавливать ее каждый раз вручную  мы можем создать свой образ.

Шаг 2. Создание своего образа

Два способа кастомизировать базовый образ – это модифицировать контейнер и сделать с него слепок, использовать специальный файл (Dockerfile) с набором команд необходимых к выполнению поверх базового образа.

2.1. Создание образа из контейнера

У нас остался контейнер развернутый из базового образа, повторим установку curl:

[root@waky ~]# docker exec -it alpine_0 sh
/ # apk add curl
fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.22/community/x86_64/APKINDEX.tar.gz
(1/9) Installing brotli-libs (1.1.0-r2)
…
(9/9) Installing curl (8.14.1-r2)
Executing busybox-1.37.0-r19.trigger
OK: 12 MiB in 25 packages
/ # exit
[root@waky ~]# 

Создадим образ из нашего контейнера с помощью команды commit, первым мы указываем имя или ID контейнера (не образа из которого сделан контейнер), а затем имя создаваемого образа:

[root@waky ~]# docker commit alpine_0 alpine_curl
sha256:346dc939b1b6ddebab2b1c879b985cf1c011c83fca0a44b65eef75235d881e0d
[root@waky ~]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
alpine_curl   latest    346dc939b1b6   7 seconds ago   16.1MB
alpine        latest    706db57fb206   2 weeks ago     8.32MB
[root@waky ~]#

Создадим новый контейнер из образа alpine_curl:

[root@waky ~]# docker run -td --name alpine_1 alpine_curl /bin/sh
de5f3dea4245cc0be6453f003eb61d73c333a0acc0c3c3fe35ec4c5d05ae1c7f
[root@waky ~]# docker ps
CONTAINER ID   IMAGE         COMMAND     CREATED          STATUS          PORTS     NAMES
de5f3dea4245   alpine_curl   "/bin/sh"   9 seconds ago    Up 8 seconds              alpine_1
fc92bbca6829   alpine        "/bin/sh"   46 minutes ago   Up 46 minutes             alpine_0
[root@waky ~]#

Проверим наличие curl в новом контейнере:

[root@waky ~]# docker exec -it alpine_1 sh
/ # curl -I https://www.google.com
HTTP/2 200
content-type: text/html; charset=ISO-8859-1
…
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
/ # exit
[root@waky ~]#

Отлично, утилита доступна сразу после создания контейнера.

Такой способ создания образа вполне рабочий, и может использоваться для небольших задач. Однако для более комплексных случаев лучше использовать Dockerfile.

2.2. Создание образа из Dockerfile

Работая даже с небольшими системами будет полезно придерживаться принципов IaC (Infrastructure-as-Code инфраструктура как код). Это подход к управлению инфраструктурой с помощью программного кода вместо ручных настроек.

Использование Dockerfile отличный тому пример, вместо ручной настройки мы используем файл с описанием шагов и компонентов.

Создадим отдельную директорию, а в ней пустой Dockerfile:

[root@waky ~]# mkdir my-app
[root@waky ~]# cd my-app/
[root@waky my-app]# touch Dockerfile
[root@waky my-app]#

Используйте любой текстовый редактор, чтобы добавить в Dockerfile следующий код:

FROM alpine:latest
RUN apk add curl

FROM указывает базовый образ взятый за основу, а с помощью RUN мы перечисляем команды которые нужно выполнить поверх базовой сборки. Результат выполнения RUN ляжет на базу новыми слоями.

Используем команду build с указанием имени нового образа (alpine_df) и директории из которой проводится сборка (. – текущая директория)

[root@waky my-app]# docker build -t alpine_df .
[+] Building 7.9s (6/6) FINISHED                                                    docker:default
 => [internal] load build definition from Dockerfile                                          0.2s
 => => transferring dockerfile: 134B                                                          0.0s
 => [internal] load metadata for docker.io/library/alpine:latest                              0.0s
 => [internal] load .dockerignore                                                             0.1s
 => => transferring context: 2B                                                               0.0s
 => [1/2] FROM docker.io/library/alpine:latest                                                0.1s
 => [2/2] RUN apk add curl                                                                    6.8s
 => exporting to image                                                                        0.4s
 => => exporting layers                                                                       0.3s
 => => writing image sha256:08bb728a3bb62325065e63732cbe47b1f420550928aa444ab0a108fdacdd8331  0.0s
 => => naming to docker.io/library/alpine_df                                                  0.0s
[root@waky my-app]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED             SIZE
alpine_df     latest    08bb728a3bb6   16 seconds ago      16.1MB
alpine_curl   latest    346dc939b1b6   About an hour ago   16.1MB
alpine        latest    706db57fb206   2 weeks ago         8.32MB
[root@waky my-app]#

Как можете видеть по выдаче, процесс сборки образа содержит шаги описанные в Dockerfile.

Создадим из нового образа контейнер и проверим, что curl работает:

[root@waky my-app]# docker run -td --name alpine_2 alpine_df /bin/sh
603024d41b234f880bf254ea121df500b199d83bb275d8792fca6fe79bb0b1d5
[root@waky my-app]# docker exec -it alpine_2 sh
/ # curl -I https://www.google.com
HTTP/2 200
content-type: text/html; charset=ISO-8859-1
…
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
/ # exit
[root@waky my-app]#

Оптимизация образа и советы

Мы рассмотрели тривиальный пример, когда Dockerfile содержит минимальное количество строк. В реальных условиях он может содержать десятки строк.

Основным правилом оптимизации образа является уменьшение количества строк RUN. Уменьшая их количество, вы тем самым уменьшения количества слоёв накладываемых поверх базового образа и ускоряете весь процесс создания.

Используйте минимальное необходимое количество команд, не оставляйте неиспользуемые элементы, например:

RUN apk add curl wget
вместо
RUN apk add curl
RUN apk add wget

Объединяйте несколько команд в одну используя &&, например:

RUN apk update && apk add curl wget
вместо
RUN apk update
RUN apk add curl wget

Не пытайтесь объединить все RUN в одну строку, это сильно усложнит читаемость файла, а как результат и вашу работу с ним.  

Удаляйте временные файлы после установки пакетов.

Используйте минимальные базовые образы, например Alpine, чтобы снизить размер.

Управление Docker-образами

Основные операции с образами простые и интуитивно понятны.

Просмотр существующих образов

Чтобы увидеть все локальные образы, выполните команду images:

[root@waky my-app]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
alpine_df     latest    08bb728a3bb6   41 minutes ago   16.1MB
alpine_curl   latest    346dc939b1b6   2 hours ago      16.1MB
alpine        latest    706db57fb206   2 weeks ago      8.32MB
[root@waky my-app]#

Переименование образа

Чтобы переименовать образ, используйте команду tag с указанием старого и нового имени:

[root@waky my-app]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
alpine_df     latest    08bb728a3bb6   42 minutes ago   16.1MB
alpine_new    latest    08bb728a3bb6   42 minutes ago   16.1MB
alpine_curl   latest    346dc939b1b6   2 hours ago      16.1MB
alpine        latest    706db57fb206   2 weeks ago      8.32MB
[root@waky my-app]#

При этом Docker создает запись с новым именем, но с тем же ID

Удаление образa

Если образ больше не нужен, его можно удалить, используя rmi:

[root@waky my-app]# docker rmi alpine_df
Untagged: alpine_df:latest
[root@waky my-app]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
alpine_new    latest    08bb728a3bb6   44 minutes ago   16.1MB
alpine_curl   latest    346dc939b1b6   2 hours ago      16.1MB
alpine        latest    706db57fb206   2 weeks ago      8.32MB
[root@waky my-app]#

Мы удалили дублирующий образ. Попробуем удалить и другой:

[root@waky my-app]# docker rmi alpine_new
Error response from daemon: conflict: unable to remove repository reference "alpine_new" (must force) - container 603024d41b23 is using its referenced image 08bb728a3bb6
[root@waky my-app]#

Нельзя удалить образ, если есть контейнер, созданный из этого образа. Сначала нужно удалить контейнер:

[root@waky my-app]# docker stop 603024d41b23
603024d41b23
[root@waky my-app]# docker rm 603024d41b23
603024d41b23
[root@waky my-app]# docker rmi alpine_new
Untagged: alpine_new:latest
Deleted: sha256:08bb728a3bb62325065e63732cbe47b1f420550928aa444ab0a108fdacdd8331
[root@waky my-app]#

В этот раз удалена не только привязка к имени, но и сам образ. Удалять образы можно как по имени, так и по ID.

Заключение

Образы Docker — это гибкая структура, основанная на слоях, которые позволяют легко управлять, обновлять и оптимизировать ваши контейнеры. Почти все операции начинаются с Dockerfile — описания шагов сборки.

Использование минимальных образов вроде Alpine позволяет создавать легкие, быстрые и надежные контейнеры, подходящие для разнообразных задач.