Создание тома данных на хосте
Первым шагом необходимо создать новый каталог для размещения тома Docker. Для этого откройте окно терминала и введите команду:
Необходимо убедиться, что вновь созданный каталог размещен в том месте, к которому пользователь Docker может получить доступ (с правами на чтение и запись). После того, как вы создали этот каталог, будьте готовы его подключить к контейнеру. Предположим, вы собираетесь развернуть контейнер, основанный на официальном образе Ubuntu, который содержит каталог с именем /data. Чтобы развернуть такой контейнер, который присоединяет внутренний каталог /data к тому в каталоге хоста ~/container-data, необходимо выполнить команду:
Вышеприведенная команда обозначает следующее:
- docker run — это основная команда, которая говорит, что мы собираемся запустить команду в новом контейнере.
- — dit — это d для режима detached, и он гарантирует, что bash или sh могут быть выделены псевдо-терминалу.
- — P публикует порты контейнеров на хосте.
- — name говорит, что далее следует имя нового контейнера.
- — v говорит, что далее объявляется том.
- ubuntu — это образ, который будет использоваться для контейнера.
Как только команда завершится, вы получите идентификатор контейнера. Убедитесь, что вы запомнили первые четыре символа этого идентификатора, так как он понадобится вам для получения доступа к оболочке bash внутри контейнера.
Теперь вы развернули контейнер, основанный на официальном образе Ubuntu, который включает каталог /data, монтирующийся на том хоста в ~/container-data.
NGINX + PHP + PHP-FPM
Рекомендуется каждый микросервис помещать в свой отдельный контейнер, но мы (для отдельного примера) веб-сервер с интерпретатором PHP поместим в один и тот же имидж, на основе которого будут создаваться контейнеры.
Создание образа
Создадим каталог, в котором будут находиться файлы для сборки образа веб-сервера:
mkdir -p /opt/docker/web-server
Переходим в созданный каталог:
cd /opt/docker/web-server/
Создаем докер-файл:
vi Dockerfile
- FROM centos:8
- MAINTAINER Dmitriy Mosk <master@dmosk.ru>
- ENV TZ=Europe/Moscow
- RUN dnf update -y
- RUN dnf install -y nginx php php-fpm php-mysqli
- RUN dnf clean all
- RUN echo «daemon off;» >> /etc/nginx/nginx.conf
- RUN mkdir /run/php-fpm
- COPY ./html/ /usr/share/nginx/html/
- CMD php-fpm -D ; nginx
- EXPOSE 80
* где:
1) указываем, какой берем базовый образ. В нашем случае, CentOS 8.
3) задаем для информации того, кто создал образ. Указываем свое имя и адрес электронной почты.
5) создаем переменную окружения TZ с указанием временной зоны (в нашем примере, московское время).
7) запускаем обновление системы.
устанавливаем пакеты: веб-сервер nginx, интерпретатор php, сервис php-fpm для обработки скриптов, модуль php-mysqli для работы php с СУБД MySQL/MariaDB.
9) удаляем скачанные пакеты и временные файлы, образовавшиеся во время установки.
10) добавляем в конфигурационный файл nginx строку daemon off, которая запретит веб-серверу автоматически запуститься в качестве демона.
11) создаем каталог /run/php-fpm — без него не сможет запуститься php-fpm.
13) копируем содержимое каталога html, который находится в том же каталоге, что и dockerfile, в каталог /usr/share/nginx/html/ внутри контейнера. В данной папке должен быть наше веб-приложение.
15) запускаем php-fpm и nginx. Команда CMD в dockerfile может быть только одна.
17) открываем порт 80 для работы веб-сервера.
В рабочем каталоге создаем папку html:
mkdir html
… а в ней — файл index.php:
vi html/index.php
<?php
phpinfo();
?>
* мы создали скрипт, который будет выводить информацию о php в браузере для примера. По идее, в данную папку мы должны положить сайт (веб-приложение).
Создаем первый билд для нашего образа:
docker build -t dmosk/webapp:v1 .
Новый образ должен появиться в системе:
docker images
При желании, его можно отправить на Docker Hub следующими командами:
docker login —username dmosk
docker tag dmosk/webapp:v1 dmosk/web:nginx_php7
docker push dmosk/web:nginx_php7
* первой командой мы прошли аутентификацию на портале докер-хаба (в качестве id/login мы используем dmosk — это учетная запись, которую мы зарегистрировали в Docker Hub). Вторая команда создает тег для нашего образа, где dmosk — учетная запись на dockerhub; web — имя репозитория; nginx_php7 — сам тег. Последняя команда заливает образ в репозиторий.
* подробнее про докера.
Запуск контейнера и проверка работы
Запускаем веб-сервер из созданного образа:
docker run —name web_server -d -p 80:80 dmosk/webapp:v1
Открываем браузер и переходим по адресу http://<IP-адрес сервера с docker> — откроется страница phpinfo:
Наш веб-сервер из Docker работает.
Основы ENTRYPOINT && CMD
Перед началом нужно кратко вспомнить, что это такое и чем отличается. Эти две директивы в Dockerfile наверняка всем известны – обе запускают процессы в контейнере. Как правило, в entrypoint используется какой-либо скрипт или команда для запуска процесса внутри контейнера, а в cmd – параметры, которые передаются в entrypoint.
Если указывать параметры в квадратных скобках, т.е. в формате exec, то внутри контейнера будет выполняться лишь процесс, который указан для запуска, и никаких оболочек запущено не будет. Это значит, что замена (substitution) переменных и их обработка невозможны – это также предпочтительный вариант для простого запуска какого-либо процесса. О более сложных моментах будет рассказано далее.
Если же указать команду для запуска без фигурных скобок, то внутри контейнера можно будет увидеть, что процесс запущен через форму shell и процесс внутри будет вида sh -c “ping ya.ru”.
Грубый пример Dockerfile для демонстрации работы exec формы:
После сборки образа с именем “ping” и дальнейшего запуска контейнера из него будет выполняться команда “/bin/ping it-lux.ru”. Домен можно переопределить, указав его при запуске контейнера, например:
Тем самым выполняется переопределение значения из параметра CMD в Dockerfile, что очень удобно – из одного образа можно запускать команды с различными аргументами. Такой способ я использовал для выполнения крон-задач в отдельном докер-контейнере, указывая на хосте в crontab через аргументы путь к скриптам при запуске docker run, которые принимал для выполнения php.
Список запущенных Docker контейнеров
Базовый формат Docker:
Чтобы вывести список всех запущенных контейнеров Docker, введите в окне терминала следующее:
Как вы можете видеть, изображение выше указывает на отсутствие запущенных контейнеров.
Чтобы вывести список всех работающих и остановленных контейнеров, добавьте параметр :
Для вывода списка контейнеров по их идентификатору используйте параметр :
Чтобы просмотреть общий размер файла каждого контейнера, используйте параметр :
Чтобы просмотреть список последних созданных контейнеров, используйте параметр :
Команда предоставляет несколько столбцов информации:
- Container ID — Уникальный буквенно-цифровой номер для каждого контейнера
- Image — Образ базовой операционной системы, на котором основан контейнер
- Command — команда, запустившая контейнер
- Created — Как давно был создан контейнер
- Status — Время работы или простои
- Ports — Указывает любые порты, перенаправляемые в контейнер для работы в сети.
- Name — Памятное имя, присвоенное программным обеспечением Docker
Прочие команды
Это общие операции, не привязанные к работе с конкретными сущностями типа образов и контейнеров. Но они тоже пригодятся при использовании Docker.
- docker version — показывает техническую информацию о самом Docker. Как о клиенте, так и о сервере.
- docker login — авторизует пользователя в реестре Docker.
- docker system prune — выполняет некую чистку, удалив из системы контейнеры, которые уже не используются, ненужные сети и образы без имен и меток.
version
К этой команде можно обратиться за подробной информацией о самом Docker. Она отобразит версию клиента и сервера Docker.
login
Об этой команде уже говорили. Она позволяет авторизоваться в реестре образов Docker Hub. Загружать туда образы и выгружать уже готовые к себе.
system prune
По умолчанию эта команда удаляет только данные без тегов, но можно настроить ее так, чтобы из системы были удалены все неиспользуемые элементы.
Допустим, чтобы стереть из Docker все образы и сверху зацепить еще тома, то надо ввести в терминал:
docker system prune –all –volumes
На этом все. Это даже больше того минимума, который нужен для работы с Docker на начальных этапах. Этих команд хватит, чтобы собрать образ, запустить контейнер и перейти к разработке. А уже по ходу дела вы сможете обращаться в документацию и обогащать свои знания.
Шаг 5: Запуск частного репозитория
Если вы хотите, чтобы образы были более защищенными, то лучше использовать частный репозиторий.
Docker Registry доступен как контейнер, т.е. он может быть запущен точно так же, как и любой другой контейнер. Так как репозиторий может содержать множество образов, хорошим решением будет прикрепить к нему том хранилища.
$ docker run -d -p 5000:5000 --restart=always --name registry -v $PWD/registry:/var/lib/registry registry
Обратите внимание на то, что контейнер запускается в фоновом режиме с использованием порта 5000, а директория registry привязана к файловой системе хоста. Используйте команду docker ps для проверки запуска контейнера. . Теперь вы можете присвоить тег локальному образу и передать его в приватный репозиторий
Но перед этим нужно скачать контейнер busybox из Docker Hub и присвоить тег ему
Теперь вы можете присвоить тег локальному образу и передать его в приватный репозиторий. Но перед этим нужно скачать контейнер busybox из Docker Hub и присвоить тег ему.
$ docker pull busybox $ docker tag busybox localhost:5000/busybox $ docker images
Команда выше подтверждает, что контейнер busybox теперь имеет тег localhost:5000, поэтому передавайте образ в приватный репозиторий:
$ docker push localhost:5000/busybox
Теперь, когда образ отправлен, можно проделать следующее – удалить его из окружения, а потом вновь загрузить из репозитория:
$ docker rmi -f localhost:5000/busybox $ docker images $ docker pull localhost:5000/busybox $ docker images
Вы выполнили полный цикл работы с образом: загрузку, присвоение тега, выгрузку в репозиторий и новую загрузку.
Рекомендации по использованию инструкции КОПИРОВАТЬ
Эта инструкция Dockerfile копирует один или несколько локальных файлов или папок в целевой образ докера.
- COPY <source>… <destination>
- COPY (этот формат требуется для путей, содержащих пробелы)
Например, используйте инструкцию COPY в Dockerfile.
Это простой пример того, как использовать инструкцию COPY в Dockerfile для создания приложения Ruby.
Он строит изображение в слоях, начиная с родительского изображения ruby: 2.5.1, используя определение FROM.
Используйте инструкцию WORKDIR, чтобы определить рабочий каталог, а затем используйте инструкцию COPY или ADD.
В этом случае, когда используется копия, он будет копировать файл из локального источника. Указывает файл в текущем каталоге в месте, определенном WORKDIR. В приведенном выше примере вторая КОПИЯ означает, что рабочий каталог в зеркале копируется из текущего каталога.
Рекомендации по использованию КОПИРОВАНИЯ для создания зеркальных слоев
Docker рекомендует использовать COPY для создания слоев изображений, сохраняя разные контексты файлов в разных слоях изображений. Это означает, что реконструкция изображения эффективна. Файлы, которые, скорее всего, будут изменены, должны находиться на нижних уровнях, а файлы, которые, скорее всего, будут изменены, должны быть добавлены последними.
Этот принцип продемонстрирован в приведенном выше примере файла dockerfile. Скопировав Gemfiles, а затем выполнив Run bundle install, вы можете использовать установленные рубиновые драгоценные камни для создания слоя изображения, который можно кэшировать. Последние две инструкции докера копируют файлы приложения в образ и используют CMD для установки команд по умолчанию.
Это означает, что при изменении каких-либо файлов приложения образ Docker можно регенерировать, используя родительский и средний уровни кеша. Это намного эффективнее, чем создание всех этих моделей с нуля.
Почему не стоит использовать инструкцию ADD
Синтаксис инструкции ADD аналогичен синтаксису COPY. Помимо копирования локальных файлов и каталогов в место назначения в образе Docker, он также имеет некоторые дополнительные функции.
- ADD <source>… <destination>
- ДОБАВИТЬ (Этот формат требуется для путей, содержащих пробелы)
Однако официальное руководство Dockerfile по лучшей практике указывает, что в большинстве случаев COPY является предпочтительной инструкцией по сравнению с ADD.
Еще одна дополнительная функция ADD — это то, что он может копировать файлы с URL-адресов, но Docker рекомендует не использовать его для этой цели.
Рекомендации по копированию файлов с URL
Если источником ADD является URL-адрес, он загрузит файл и скопирует его в место назначения в образе докера. Докер считает, что использование добавления для копирования с URL-адреса обычно неэффективно, и для включения необходимых удаленных файлов лучше использовать другие стратегии.
Например, следует избегать следующих действий:
Вместо этого выполните следующие операции:
Остановка, запуск и перезапуск контейнеров
Вы можете остановить запуск контейнеров и запустить остановленный контейнер. Существует несколько различий между запуском остановленного контейнера и запуском нового экземпляра того же образа:
- Вы используете одни и те же переменные среды, тома, порты и другие аргументы исходной команды запуска.
- Вам не нужно создавать еще один контейнер.
- Если остановленный экземпляр изменил свою файловую систему, запущенный контейнер будет использовать то же самое.
Давайте остановим контейнер nginx, а затем запустим его. Когда вы ссылаетесь на контейнер, вы можете использовать его имя или однозначный префикс своего идентификатора. Обычно я называю свои контейнеры долгожители, поэтому у меня есть значащий дескриптор и не нужно иметь дело с автогенерируемыми именами Docker или префиксами идентификаторов контейнеров.
ОК. Nginx остановлен (статус « Exited»). Давайте начнем:
Перезапуск работающего контейнера — это еще один вариант, который эквивалентен , за которым следует .
Как использовать ресурсы контейнера ¶
По умолчанию контейнер закрыт от любых контактов извне. Вы не можете ни скопировать в него файл, ни подключиться к сокету. Он закрыт. Но при запуске контейнера можно пробросить порт или папку. Тогда к нему можно будет подключиться, добавить файл или что-то скачать. Всё это делается при создании (запуске) контейнера через аргументы.
Проброс портов
Для примера запустим контейнер с Debian 9 и пробросим локальный порт 3132 на 80 порт контейнера:
Пояснения к команде:
- — создаёт и запускает новый контейнер.
- — подключает виртуальную консоль. Это нужно, чтобы команда не завершала работу, иначе контейнер остановится.
- — запускает выполнение контейнера в фоне. Без этого аргумента консоль будет ждать, когда контейнер остановится (для остановки придётся использовать другую консоль).
- — использовать в качестве процесса системную утилиту . Просто потому, что она не завершится пока не закроется stdin, а значит и контейнер будет работать.
- — уникальное имя, которое используется для управления контейнером. Если его не указывать, то докер сам придумает какое-нибудь имя.
- — проброс портов. Сначала надо указать порт машины (можно указать вместе с IP), потом порт в контейнере.
Общий принцип запуска контейнеров довольно простой:
Аргументы, параметры и тег необязательны, их можно опускать. Но нужно помнить, что без аргументов образ сам по себе не пробрасывает порты и папки. Это всегда делается через аргументы при создании контейнера.
Теперь давайте рассмотрим, как выполнять команды внутри контейнера. Для примера установим и запустим консольный сервер php 7:
Пояснения к команде:
- — выполняет команду внутри запущенного контейнера.
- — подключает виртуальную консоль. Без этого аргумента вывод будет неправильным.
- — подключает ввод. Без него не будет работать клавиатура.
- — имя контейнера, в котором выполняется команда.
- — команда, которая будет выполнена внутри контейнера.
Чтобы проверить, работает сервер или нет, нужно подключиться на 3132 порт основной машины, например так:
В этом примере я использовал две разные консоли. На одной я запускал сервер, а на другой curl. Ещё можно использовать браузер, если докер установлен у вас в системе.
Проброс папки
В предыдущем примере я создал index.php прямо в контейнере. Это не самый удобный способ разработки проектов в через докер. Во-первых, много файлов так не создашь, во-вторых ими сложно управлять, а, в-третьих, если удалить контейнер, они тоже удалятся. Чтобы решить эти проблемы, можно пробросить (примонтировать) папку из реальной машины в виртуальный контейнер. Делается это, как всегда, при создании контейнера, через аргумент .
Прежде, чем начать что-то менять, надо удалить старый контейнер:
Теперь подготовим наш «проект»:
А теперь запускаем контейнер с пробросом папки проекта:
Если вы помните, я удалил контейнер, в котором был установлен php, а это значит, что мне заново придётся установить пакет php7.0-cli:
Теперь в контейнере есть и проект, и php, можно запускать сервер:
Теперь проверяем, как работает наш проект:
Для наглядности давайте создадим ещё один файл в «проекте», чтобы удостовериться, что всё работает как надо:
Должно вывести что-то вроде этого:
Если у вас получилось, смело пишите в резюме, что владеете докером!
Код приложения
В открывшемся проекте рядом с главным классом, содержащим метод main, создаём ещё один класс — контроллер с методом hello ().
Класс помечен аннотацией @RestController, означающей, что он предназначен для обработки web-запросов. А метод помечен аннотацией @GetMapping c адресом «/» — перейдя по нему (выполнив get-запрос к http://localhost:8080/), мы получим сообщение «Hello Docker!»
Теперь открываем терминал и вводим команду:
Она упакует приложение в jar-файл и запустит его. Чтобы убедиться в корректности работы приложения — перейдём на http://localhost:8080/ в браузере и увидим заветное сообщение.
Теперь создаём файл с именем Dockerfile в корне проекта, который содержит инструкции для сборки образа со следующим текстом:
Вот что происходит, когда мы вводим этот код:
Команда | Описание |
---|---|
FROM adoptopenjdk/openjdk11:alpine-jre | Oбраз создаётся на основе alpine linux с установленной openjdk11 |
ARG JAR_FILE=target/spring-docker-simple-0.0.1-SNAPSHOT.jar | Переменной JAR_FILE присваивается путь к jar- архиву |
WORKDIR /opt/app | Назначаем рабочую директорию, в которой будут выполняться дальнейшие команды (перемещаемся в папку app) |
COPY ${JAR_FILE} app.jar | Наш jar-файл, указанный в JAR_FILE, копируется в папку app, и копии задаётся имя app.jar |
ENTRYPOINT | jar-файл запускается, собирается команда java -jar app.jar из заданной рабочей директории |
После этого в терминале вводим команду, с помощью которой собираем образ и запускаем контейнер.
Точка в конце важна, она указывает на расположение Dockerfile (символ «точка» означает текущую директорию. Проверьте, что образ создан командой docker images. Вывод должен быть таким:
Запускаем контейнер командой:
Опция -d означает старт процесса в фоновом режиме. Опция -p тоже важна — дело в том, что контейнер собирается в полностью изолированном окружении. Тот факт, что приложение внутри контейнера запущено на порту 8080, не означает, что оно доступно вне контейнера на этом порту.
Требуется явно указать, что порту 8080 в контейнере (здесь второе значение — это порт, на котором работает наше приложение в контейнере) соответствует порт 8080 на локальной машине, который будет использоваться при обращении к контейнеру. Поэтому пишем через двоеточие -p 8080:8080.
Теперь введём в терминале команду:
Не делайте привязку к определенному UID
Запускайте контейнеры без полномочий root, но также не делайте этот UID пользователя обязательным. Почему?
-
OpenShidt по умолчанию использует произвольные UID при запуске контейнеров.
-
Принудительное использование определенного UID требует изменения прав при монтировании папок с хостовой системы. Если вы запустите контейнер (параметр -u в докере) с UID хоста, это может нарушить работу службы при попытке чтения или записи из папок в контейнере.
Возникнут проблемы при запуске этого контейнера с UID отличающимся от myuser, так как приложение не сможет записывать в папку folder.
Не нужно жестко задавать путь только для пользователя myuser. Вместо этого можно записать временные данные в /tmp (где любой пользователь может писать, благодаря разрешениям sticky bit). Сделайте ресурсы доступными для чтения (0644 вместо 0640) и убедитесь, что все работает, если UID измениться.
В этом примере наше приложение будет использовать путь из переменной среды APP_TMP_DATA. Путь /tmp позволяет приложению запускаться от любого UID и продолжить записывать временные данные в папку /tmp. Наличие пути в переменной окружения не обязательно, но позволяет избежать проблем при настройке и монтировании разделов для долговременного хранения данных.
3. Назначить root владельцем исполняемых файлов и запретить изменять эти файлы
Рекомендуется назначать root владельцем каждого исполняемого файла в контейнере, даже если он выполняется пользователем без полномочий root. Также исполняемые файлы не должны быть доступны на запись всем пользователям.
Это предотвратить возможность изменения существующих бинарных файлов и скриптов, что может быть использована при различных атаках. Следуя этой рекомендации контейнер должен оставаться неизменяемым. Такой контейнер не изменяет код приложения во время выполнения, что позволяет избежать ситуации, при которой запущенное приложение случайно или злонамеренно изменяется.
Следуя этой рекомендации старайтесь избегать подобной ситуации:
В большинстве случаев вы можете не использовать опцию(в том числе запуск команды ). Пользователю app требуются только права на выполнение, при этом быть владельцем файла не обязательно.
Пункт 2.5 Почаще обновляйте свои образы
Каждый открытый порт в вашем контейнере-это открытая дверь в вашей системе. Публикуйте наружу только те порты, которые нужны вашему приложению, и избегайте таких портов, как SSH (22).
Обратите внимание, что хотя Dockerfile предлагает , эта инструкция несет скорее информационную смысловую нагрузку и предназначена для документирования. Предоставление доступа к порту не позволяет автоматически подключаться ко всем ПУБЛИКУЕМЫМ портам при запуске контейнера (если только вы не используете docker run —publish-all)
Вы должны указать публикуемые для работы порты, на этапе запуска контейнера.
Используйте EXPOSE для документирования только необходимых портов в файле Dockerfile, а затем придерживайтесь этих данных при запуске и эксплуатации.
Пункт 3.2 ADD, COPY
Обе инструкции, и ADD, и COPY предоставляют схожий функционал при создании Dockerfile. Хотя команда COPY является более очевидной по смыслу.
Используйте COPY, если вам действительно не нужна , например для добавления файлов из URL-адреса или из файла tar. COPY более предсказуема и менее подвержена ошибкам. (Условно — результат работы copy более предсказуем — например вы хотите скопировать архив внутрь образа, однако если copy просто скопирует как есть, то ADD, если она распознает тип архива, она может его распаковать и вы обнаружите в итоге не то, что ожидали. Получается что ADD добавляет некоторую неочевидность, некоторый уровень “магии”, что не есть хорошо)
В некоторых случаях предпочтительнее использовать инструкцию RUN вместо ADD для загрузки пакета с помощью curl или wget, распаковать его, а затем удалить исходный файл за один шаг, сократив количество слоев (чем проделывать это через ADD, опять же для большей предсказуемости).
Многоступенчатые сборки также решают эту проблему и помогают вам следовать рекомендациям Dockerfile, позволяя копировать только окончательные извлеченные файлы с предыдущего этапа.
Шаг 4: Создание образов
Вы можете не только запускать существующие образы, но и создавать собственные, а также хранить их в репозитории.
Новые образы можно создавать из существующих контейнеров. Запустите еще раз контейнер httpd и измените начальный документ:
$ docker run -p 80:80 --name web -d httpd $ docker exec -it web /bin/bash $ cd htdocs $ echo '<h1>Welcome to my Web Application</h1>' > index.html $ exit
Контейнер запущен с отредактированным файлом index.html. Проверить это можно командой curl localhost.
Перед тем как превратить контейнер в неизменяемый образ, лучше остановить его:
$ docker stop web
А вот после этого можно ввести команду commit, которая фактически превратит контейнер в неизменяемый образ.
$ docker commit web doweb
Подтвердите создание образа командой docker images. Она покажет образ doweb, который только что был создан.
Вы можете присвоить образу тег, а также поместить его в Docker Hub. Для этого выполните следующие команды (вместо your_docker_hub_username нужно написать имя пользователя вашего Docker Hub):
$ docker login $ docker tag your_docker_hub_username/doweb $ docker push
Теперь новый образ можно найти в Docker Hub или в командной строке.
Редактирование образа
В примере выше мы рассмотрели создание нового образа с нуля. Также, мы можем взять любой другой образ, отредактировать его и сохранить под своим названием.
Скачаем образ операционной системы CentOS:
docker pull centos:latest
Войдем в скачанный образ для его изменения:
docker run -t -i centos:latest /bin/bash
Внесем небольшие изменения, например, создадим учетную запись:
# useradd dmosk -G wheel -m
# passwd dmosk
# exit
* в данном примере мы создали пользователя dmosk и задали ему пароль.
Коммитим образ:
docker commit -m «Add user dmosk» -a «Dmitry Mosk» 8f07ef93918f centos:my
* где -m — параметр для указания комментария; -a — указывает автора; 8f07ef93918f — идентификатор контейнера, который был нами изменен (его можно было увидеть в приглашении командной строки); centos:my — название нашего нового образа.
Новый образ создан.