У меня есть несколько ультралегковесных PHP-скриптов, предоставляющих простой HTTP-API для тех или иных целей. Например, один из таких скриптов выполняет функции управления сетью (реализует протоколы SNMP, Telnet и т.д.) с доступом по HTTP-API. Но чтобы клиент мог эксплуатировать такой простой скрипт, ему необходимо установить и настроить WEB-сервер, РНР со всеми необходимыми расширениями, системные зависимости, такие как iproute2 и прочие и для многих клиентов это оказалось нетривиальной задачей. Так родилась идея поместить в один Docker-образ и скрипт и всё окружение, вместе со всеми необходимыми настройками. По идее, пользователь должен выполнить простую команду docker run ...
чтобы всё это магическим образом сразу же заработало без какой-либо необходимости что-то устанавливать и настраивать самостоятельно.
- Нам нужен крайне легковесный Docker-образ, включающий в себя сам PHP-скрипт, php-fpm, nginx и остальное окружение, необходимое для работы.
- Доступ по HTTP должен быть только к единственному файлу index.php.
- Службы внутри контейнера должны работать от имени непривилегированного пользователя nobody, чтобы сделать контейнер чуточку безопасней.
- Так как клиенты разбросаны по всему миру, нужно чтобы часовой пояс клиента был корректным внутри контейнера, в том числе и для PHP.
- Все сообщения системных процессов (nginx, php и т.д.) должны выводиться на стандартный вывод
stdout
иstderr
, как это требуется для процессов, работающих в контейнерах. Но выводить туда же логи самого PHP-скрипта может быть не совсем целесообразно, хотя и вполне допустимо. Всё же для логов PHP-скрипта условимся использовать отдельный лог-файл, который к тому же должен автоматически ротироваться без участия пользователя.
Вроде бы всё. Поехали.
Следующее описание будет для нового проекта, который только создается, без привязки к моему конкретному примеру. Итак, начнем с самого минимума.
.
├── app
│ └── public
│ └── index.php
├── Dockerfile
└── rootfs
├── etc
│ ├── crontabs
│ │ └── nobody
│ ├── logrotate.conf
│ ├── nginx
│ │ └── nginx.conf
│ ├── php7
│ │ ├── conf.d
│ │ │ └── custom.ini
│ │ └── php-fpm.d
│ │ └── www.conf
│ └── supervisor
│ └── conf.d
│ └── supervisord.conf
└── usr
└── bin
└── docker-entrypoint.sh
Показная здесь структура файлов подразумевает, что ваш PHP-проект на локальном компьютере находится в подкаталоге app
текущего каталога (например, /home/coolhacker/my-cool-project/app
). Если это не так — не страшно. Нужно будет чуть больше манипуляций при создании образа, но не существенно. Далее будет понятно, что и где будет отличаться.
В данном примере файлы PHP находятся в подкаталоге app
проекта, в котором, в свою очередь, находится подкаталог public
, содержащий файл index.php
— к нему будет указан путь в конфигурации nginx.
Всё это дерево каталогов и файлов представляет собой контекст сборки Docker-образа.
Начинать будем с создания файла Dockerfile
в корне каталога. В нашем примере это /home/coolhacker/my-cool-project/Dockerfile
.
Для сборки собственного образа лучше всего брать исходный образ Alpine. Я пробовал построить свой образ на основе образа php-fpm-alpine, но в итоге остановился на использовании базового образа Alpine в качестве исходного. Первой строкой Dockerfile
прописываем инструкцию:
FROM alpine:3.14
Это означает, что за исходный будет взят образ alpine версии 3.14.
Далее нужно установить весь необходимый софт.
Здесь остановимся подробней.
Во-первых, нужно точно понимать, что и зачем добавляется в образ. И, если можно избежать добавления лишних файлов — лучше так и поступить. К счастью, идея Alpine заключается как раз в том, что сама операционная система и дополнительные пакеты содержат минимум файлов, необходимый для их работы. Некоторые пакеты вообще могут состоять из одного единственного файла (как, например, php8). Поэтому важно понимать, что при установке php вы получите только PHP без расширений вообще. То есть только то, что фактически входит в ядро. Не больше.
Во-вторых, все пакеты легко можно отыскать на сайте https://pkgs.alpinelinux.org/ либо по названию пакета, либо по файлу, который необходим.
В-третьих, расширения PHP лучше устанавливать из пакетов, но можно также воспользоваться и docker-php-ext-install
, однако, в таком случае придется устанавливать все зависимости вручную из пакетов.
Чтобы установить пакеты, в Alpine используется команда apk add
после которой идут опции и список пакетов, разделенный пробелом. Пока что добавим в Dockerfile
выполнение самой команды и укажем две опции --update
чтобы обновить кэш репозитория и --no-cache
чтобы не кэшировать индекс (он нам не понадобится внутри образа). Для выполнения команд используется инструкций RUN
:
FROM alpine:3.14
RUN apk add --no-cache --update \
Обратный слэш в конце указывает на то, что команда не завершается и продолжается со следующей строки. Так принято составлять длинные команды, чтобы не писать их целиком в одной длинной нечитаемой строке. Простое удобство форматирования длинной строки.
Далее будем добавлять строки с необходимыми пакетами.
Установим пакет nginx. Просто добавим название пакета, чтобы получилось:
RUN apk add --no-cache --update \
nginx \
В данном примере показана установка php7 (по факту будет установлена последняя версия 7.4) и некоторых базовых расширений. Добавляем их:
RUN apk add --no-cache --update \
nginx \
php7 \
php7-fpm \
php7-ctype \
php7-mbstring \
php7-json \
php7-opcache \
Если вы хотите использовать php8 — не проблема. Только следует помнить, что пакет php8 включает один исполняемый файл с именем php8
, а пакет php7 включает два исполняемых файла php
и php7
. Это может быть важно, если вы запускаете какие-то сценарии php из консоли.
Если вам нужны другие расширения — находите их здесь и добавляйте в список.
Далее добавим следующие системные утилиты, которые понадобятся для работы контейнера:
- curl — для использования в директиве
HEALTHCHECK
вDockerfile
для проверки состояния контейнера. Можно не использовать эту директиву, но тогда есть шанс, что зависший php-fpm или nginx остановит работу вашего сервиса, в то время как контейнер будет считаться выполняемым (running); - tzdata — для обеспечения поддержки часовых поясов внутри контейнера;
- tini — не обязательная, но очень желательная утилита — инициализатор процессов init, специально разработанный для запуска в контейнере. Позволяет корректно управлять процессами внутри контейнера и убивать зомби-процессы, не позволяя исчерпать пространство PID. Вместо использования этой утилиты можно запускать контейнер с параметром
--init
, однако добавление этой утилиты ни коем образом не вредит контейнеру в любом случае; - supervisor — так как идеология запуска процессов в линукс-контейнерах подразумевает запуск только одного процесса на контейнер, то в качестве этого одного процесса можно использовать супервизор, задача которого лежит в запуске остальных процессов;
- logrotate — инструмент для ротации логов, если таковые есть. Вовсе не обязательно использовать ротацию логов внутри контейнера и можно сделать это внешним logrotate из операционной системы хоста. Либо же вообще не писать логи в файлы, а писать их на стандартный вывод. В общем, если необходимо иметь самодостаточный контейнер, который сам еще и логи приложения ротирует, то добавляем;
- dcron — легковесный crontab. Необходим для периодического запуска процессов внутри контейнера. В данном примере он будет запускать logrotate для периодической ротации логов. Также может пригодиться для периодического запуска каки-то фоновых процессов вашего приложения. Если ни в чем из этого нет необходимости, можно не устанавливать;
- libcap — инструмент, который используется для запуска cron от имени непривилегированного пользователя. Так как все процессы в целях безопасности в контейнере будут запускаться от имени пользователя
nobody
, то и cron должен запускаться от имени этого пользователя, что по умолчанию невозможно. Данный инструмент позволяет решить эту задачу. Если не устанавливали dcron, то в этом инструменте тоже нет необходимости.
Добавляем далее в инструкцию RUN
эти инструменты:
RUN apk add --no-cache --update \
nginx \
php7 \
php7-fpm \
php7-ctype \
php7-mbstring \
php7-json \
php7-opcache \
curl \
tzdata \
tini \
supervisor \
logrotate \
dcron \
libcap \
Сперва нужно разобраться с dcron. Нам нужно, чтобы он запускался от имени непривилегированного пользователя (что обычно невозможно). Для этого мы установили libcap. Назначим владельца файла crond
нашего пользователя nobody
и изменим setuid-бита на capabilities. Если dcron не устанавливали, то и строки эти добавлять не нужно. Если устанавливали, то добавляем:
&& chown nobody:nobody /usr/sbin/crond \
&& setcap cap_setgid=ep /usr/sbin/crond \
Теперь создадим все необходимые каталоги. Как минимум каталог /app
для PHP-файлов. В нашем примере также существует каталог /logs
для размещения файлов логов в нем. Добавляем в Dockerfile
:
&& mkdir -p /app /logs \
Последим действием будет очистка от ненужных файлов. Нужно удалить все временные файлы, все логи, кэши, чтобы образ занимал как можно меньше места. Также удалить все базовые кронтабы (конфиги для cron) и удалить каталог с подключаемыми конфигами ротатора логов logratate — они нам точно не нужны, т.к. мы всё настроим в одном файле. Это ведь контейнер. Добавляем далее:
&& rm -rf /tmp/* \
/var/{cache,log}/* \
/etc/logrotate.d \
/etc/crontabs/* \
/etc/periodic/daily/logrotate
Как видно, последняя строка не содержит обратного слеша в конце — это означает конец строки.
Целиком весь файл Dockerfile
на данный момент выглядит следующим образом:
FROM alpine:3.14
RUN apk add --no-cache --update \
nginx \
php7 \
php7-fpm \
php7-ctype \
php7-mbstring \
php7-json \
php7-opcache \
curl \
tzdata \
tini \
supervisor \
logrotate \
dcron \
libcap \
&& chown nobody:nobody /usr/sbin/crond \
&& setcap cap_setgid=ep /usr/sbin/crond \
&& mkdir -p /app /logs \
&& rm -rf /tmp/* \
/var/{cache,log}/* \
/etc/logrotate.d \
/etc/crontabs/* \
/etc/periodic/daily/logrotate
Такая длинная строка, состоящая из множества разных команд создает один слой в образе Docker. Чем меньше слоев — тем лучше.
Поехали дальше.
Теперь нам нужно подготовить различные файлы конфигураций, которые будут скопированы в образ с сохранением иерархии. Создадим каталог rootfs
там же, где находится файл Dockerfile
и всю структуру каталогов внутри (подкаталоги etc
, usr
и все подкаталоги). Если вам не нужен cron, то не создавайте каталог crontabs
и файл крона nobody
внутри него; если не нужна ротация логов, не создавайте файл logrotate.conf
. Всё остальное повторяйте в точности как есть.
Далее содержимое каталога rootfs
нужно скопировать в корень Docker-образа. Добавляем в Dockerfile
следующую инструкцию:
COPY rootfs /
Далее подробней о каждом файле из rootfs
:
Внутри crontabs
находятся файлы расписаний cron. Имя файла соответствует имени пользователю, от которого будут запущены процессы внутри этого файла расписания. Так как пользователь, от имени которого будут работать все процессы внутри контейнера — nobody, то и файл должен иметь такое же имя. Внутри файла одна единственная строка, запускающая службу logrotate ежедневно в 6:30 утра:
30 6 * * * /usr/sbin/logrotate -v /etc/logrotate.conf
Если вам нужны какие-то еще задачи — добавляйте строки в этот файл, как обычно в crontab.
Внутри nginx
находятся файлы конфигураций сервера Nginx. Мы изменим только главный файл nginx.conf
, остальные оставим как есть. Прямо в главный файл впишем настройку server
, так как внутри контейнера он все равно будет один — нет смысла загружать группу конфигураций, как это сделан по умолчанию.
Логирование в этом файле настраивается на стандартный вывод — /dev/stdout
и /dev/stderr
.
В nginx.conf
внутри блока server
настраивается единственный в контейнере сервер. Причем настраивается таким образом, что доступ разрешен только к файлу index.php (и к корню сервера, который считается тем же индексом). Вместо такой конфигурации можно использовать любую другую — как необходимо в вашем конкретном случае. Помимо этого настраивается location ~ ^/(fpm-status|fpm-ping)
, необходимый для контроля состояния пула php-fpm.
Подключения принимаются на порту 8080, так как nginx будет работать не от суперпользователя и в этом случае желательно использовать порты с номерами выше 1023 во избежание проблем с правами доступа.
В файле custom.ini
настраивается вся необходимая конфигурация php. Вывод ошибок выполняется в /dev/stderr
Обратите внимание на настройку часового пояса — он читается из переменной окружения ${TZ}
. К ней мы еще вернемся позже.
etc/php7/php-fpm.d/www.conf
Файл www.conf
содержит настройки пула fpm. В качестве назначения для сообщений об ошибках также указан /dev/stderr
. Кроме того, используется файловый сокет (который указан в nginx.conf
), включаются служебные эндпоинты для контроля состояния fpm пула. Настраивается менеджер процессов на усредненные значения (так как мы не знаем, какие ресурсы будут доступны контейнеру у клиента).
Так как в контейнере можно запустить только один основной процесс, а нам нужно целых три (nginx, php, cron), воспользуемся супервизором — он будет тем самым основным процессом, который породит остальные нужные процессы.
Здесь стоит отметить важный нюанс: процессы, запускаемые в контейнере, должны работать в foreground, то есть не должны демонизироваться. Обычно у каждого демона есть такой режим работы. Так что, если вам нужно запустить еще какой-то процесс в контейнере, обязательно учитывайте это требование.
Файл supervisord.conf
помимо основной секции настроек содержит три одинаковые секции, отличающиеся только командой, которая будет выполнена. Для каждого из сервисов указывается стандартный вывод в качестве потока вывода журнала.
Этот файл содержит минимальную базовую конфигурацию ротации лога, а также одну единственную настройку размещения логов с пустыми настройками (будут браться настройки по умолчанию). Если вам нужно задать какие-то иные параметры ротации — укажите их в базовых настройках. Этого будет достаточно. Если же вам нужно ротировать разные файлы журналов по-разному, то внутри фигурных скобок пропишите отличающиеся параметры — они перекроют параметры по умолчанию.
После того, как файлы скопированы, необходимо изменить владельцев некоторых из них, а также каталогов, которые были созданы ранее. Добавляем в Dockerfile
следующую команду:
RUN chown -R nobody:nobody /app \
&& chown -R nobody:nobody /logs \
&& chown -R nobody:nobody /run \
&& chown -R nobody:nobody /var/lib \
&& chown -R nobody:nobody /var/log/nginx \
&& chown -R nobody:nobody /etc/crontabs
Далее нужно сменить пользователя на nobody, так как все последующие действия должны быть выполнены от его имени. Добавляем в Dockerfile
следующую инструкцию:
USER nobody
Делаем каталог /app
внутри образа текущим рабочим каталогом. При запуске контейнера из этого образа этот каталог будет каталогом по умолчанию, относительно которого будет выполняться все в контейнере:
WORKDIR /app
Так как все рабочие PHP-файлы находятся в подкаталоге app
текущего каталога, то скопировать их в каталог /app
образа можно за одно действие. Здесь важно то, что владелец файлов и подкаталогов должен быть nobody. Добавляем в Dockerfile
следующую инструкцию:
COPY --chown=nobody:nobody app /app
Если у вас PHP-файлы расположены не в подкаталоге (как в данном примере в подкаталге app), то в простейшем случае придется перечислить все подкаталоги и файлы в инструкции COPY
. Последний аргумент — путь назначения.
Если приложение ведет файл журнала в каталоге /logs
, как это было задано в изначальной задаче, то было бы правильным разместить этот каталог за пределами контейнера в файловой системе хоста, даже если к нему не будет осуществляться доступ напрямую с хоста.
Ну и единственный порт, который принимает подключения — это порт 8080. Так как мы запускаем nginx не от имени суперпользователя, то не желательно либо даже невозможно использовать порты с номерами ниже 1024 (они доступны только пользователю root). Поэтому используем привычный порт 8080. В любом случае при запуске контейнера можно опубликовать этот порт на любой другой порт хоста, например, на порт 80.
Добавляем в Dockerfile
:
VOLUME "/logs"
EXPOSE 8080
Инструкция ENTRYPOINT
определяет точку входа в контейнер. Команду, вместе с аргументами, которая должна быть выполнена в контейнере при его старте. В качестве дополнительных аргументов для точки входа передаются команда и ее аргументы, указанные при старте контейнера.
Обычно точкой входа является скрипт с именем наподобие entrypoint.sh
, который инициализирует контейнер при старте. Мы будем в такой инициализации устанавливать переменную окружения ${TZ}
, в которую запишем часовой пояс. Обычно, но не всегда, в самом конце скрипта entrypoint.sh присутствует команда exec
которая запускает переданную в аргументах команду.
Наш скрипт находится в rootfs по пути usr/bin/docker-entrypoint.sh
и будет скопирован вместе с остальными файлами на сервер. Этот скрипт обязательно должен быть исполняемым (то есть иметь установленный флаг x
).
Для большинства случаев будет достаточно добавить в Dockerfile
следующую команду:
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
Это значит, что если запустить контейнер, указав, например, команду pwd
, то фактически при старте контейнера будет выполнена команда /usr/bin/docker-entrypoint.sh pwd
.
Так как у нас установлен специальный инициализатор процессов для контейнеров — tini, воспользуемся им в точке входа. Вместо примера выше добавляем в Dockerfile
следующую строку:
ENTRYPOINT ["/sbin/tini", "--", "/usr/bin/docker-entrypoint.sh"]
Теперь необходимо указать выполняемую по умолчанию команду. Это та основная команда, которая будет выполняться в контейнере по умолчанию, если при запуске не указать её явно в аргументах docker run
.
Для нашего контейнера таким основным процессом будет супервизор. Именно он должен запуститься как главный процесс и затем он породит все необходимые процессы (nginx, php-fpm, crond), которые будут работать в foreground без демонизации.
Эту команду можно легко переопределить, передав другую команду в качестве главного аргумента команд docker run
или docker exec
.
Однако стоит понимать, что в любом случае команда будет передана в аргументы скрипта, указанного как ENTRYPOINT
.
Добавим в Dockerfile
команду по умолчанию:
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
Теперь при старте контейнера будет фактически вызываться точка входа вместе с командой, что будет выглядеть следующим образом:
/sbin/tini -- /usr/bin/docker-entrypoint.sh /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
На этом можно было бы и закончить, но так как наш контейнер будет выполнять несколько различных процессов, то есть вероятность того, что какой-то из них перестанет работать так, как от него ожидается, в то время как основной процесс (супервизор) будет продолжать работу. В таком случае контейнер будет отображаться как работающий (running), но фактически он будет нерабочий.
Благодаря инструкции HEALTHCHECK
можно запускать периодическую проверку жизнеспособности контейнера. В данном примере будем обращаться каждые 30 секунд (по умолчанию) к эндпоинту http://127.0.0.1:8080/fpm-ping
. Если последние 3 проверки (по умолчанию) будут провалены, контейнер будет считаться нерабочим.
Данная проверка позволяет проверить одновременно работу nginx и php-fpm, что довольно неплохо, но вместо этого можно пойти еще дальше и реализовать подобный проверочный эндпоинт у себя в PHP-скрипте, чтобы наверняка проверить работоспособность целиком всей службы, работающей в контейнере.
Добавим в Dockerfile
инструкцию:
HEALTHCHECK --timeout=10s CMD curl --silent --fail http://127.0.0.1:8080/fpm-ping
На этом создание Dockerfile
можно считать завершенным.
Здесь будут рассмотрен простой способ создания образа, его распространения и запуска. Справку по всем командам и параметрам можно получить из официальной документации.
При выполнении сборки в файловое пространство демона containerd
копируются все файлы из контекста. В нашем случае контекст — это все каталоги и файлы, расположенные там же, где и файл Dockerfile
, но если этот файл расположен в другом месте — это можно указать отдельно. В общем случае этот файл располагается там же, где и остальные файлы и здесь рассматривается именно такой вариант.
Если есть какие-либо файлы или каталоги, попадание которых в пространство сборки не имеет смысла, целесообразно добавить их имена в специальный файл .dockerignore
, синтаксис которого целиком аналогичен синтаксису .gitignore. Обычно этот файл, как минимум, дублирует .gitignore, однако чаще содержит больше записей, ведь игнорируемых при сборке Docker-образа файлов может быть значительно больше, чем игнорируемых для репозитория.
Для построения образа на основании созданного Dockerfile
необходимо запустить команду:
docker build --pull --tag=my-app-bundle-image .
Здесь:
--pull
— загружать свежие версии Docker-образов, от которых зависит сборка.--tag=my-app-bundle-image
— финальному образу будет автоматически назначена данная метка. Если образ планируется разместить в Docker-хабе, метка должна состоять из имени пользователя (предприятия) Docker-хаба и через слеш имени образа. Также через двоеточие можно указывать версию. Если версию не указать, она автоматически устанавливается вlatest
. У одного и того же образа может быть сколько угодно меток. Чаще всего это используется для версионирования, когда новая версия получает новый номер, а такжеlatest
..
— точка в конце строки указывает на путь к контексту. В данном случае это «текущий каталог». Так как не указан параметр--file
, тоDockerfile
будет использоваться из контекста.
После выполненя команды получаем готовый Docker-образ, который можно эксплуатировать.
Для запуска контейнера из образа служит комнада docker run
. В качестве аргументов этой команде в нашем случае необходимо обязательно передать каталог, который будет отображаться на каталог /logs
внутри контейнера (для записи логов), а также опубликовать порт контейнера на порт хоста (по умолчанию в режиме моста).
Сперва необходимо создать каталог для логов. Создадим его прямо здесь, после чего добавим его в .gitignore
и в .dockerignore
:
mkdir -p log
chmod 777 log
Помимо этого мы передадим некоторые дополнительные аргументы, значение которых будет описано далее.
Для проверочного запуска контейнера из образа выполните команду:
docker run -it --name=my-app-container \
-p 80:8080 \
-v $(pwd)/log:/logs \
-v /etc/localtime:/etc/localtime:ro \
-v /etc/timezone:/etc/timezone:ro \
my-app-bundle-image
Где:
-it
— запуск в интерактивном режиме (подключение STDIN/STDOUT) и с созданием псевдо-TTY.--name=my-app-container
— контейнер после запуска будет иметь имя my-app-container, чтобы можно было обращаться к нему по этому заведомо известному имени. Если не задать имя, то оно будет выбрано случайным образом.-p 80:8080
— публикация порта контейнера 8080 на 80й порт локального хоста.-v $(pwd)/log:/logs
— подключение локального томаlog
к каталогу/logs
внутри контейнера.-v /etc/localtime:/etc/localtime:ro
и-v /etc/timezone:/etc/timezone:ro
— транслировать внутрь контейнера файлы с часовым поясом и локальным временем. Это позволит получить часовой пояс внутри контейнера точно такой же, как и на хосте, а при старте контейнера скрипт точки входа дополнительно установит переменную окружения с часовым поясом, которую использует php.my-app-bundle-image
— имя образа (метка), на базе которого будет запущен контейнер
Запустится контейнер. В консоль начнут поступать сообщения о запуске процессов супервизором. Попробуйте открыть страницу в браузере http://127.0.0.1/ — вы увидите вывод phpinfo()
. Также будет создан файл журнала, в который будет помещена одна строка. Проверьте это.
Чтобы контейнер работал в фоне, автоматически запускался при старте системы и перезапускался при падении, аргументы запуска следует немного изменить. Остановите выполнение текущего контейнера комбинацией клавиш Ctrl+C
и выполните следующую команду:
docker run -d --name=my-app-container \
--restart=always
-p 80:8080 \
-v $(pwd)/log:/logs \
-v /etc/localtime:/etc/localtime:ro \
-v /etc/timezone:/etc/timezone:ro \
my-app-bundle-image
Здесь:
-d
— запускать контейнер в фоне (поэтому ключи-it
больше не нужны);--restart=always
— запускать контейнер при старте системы и всегда перезапускать при сбое (бесконечное число попыток). Вместо этого можно задать другие варианты.
Контейнер будет запущен в фоне и вы получите командную строку оболочки. Попробуйте снова открыть страницу в браузере, понаблюдайте за логом при многократном обновлении страницы.
Теперь, чтобы остановить контейнер, выполните команду:
docker stop my-app
Контейнер при этом не удаляется. Чтобы его снова запустить. выполните команду:
docker start my-app
Самый простой способ распространения — загрузка в Docker-hub. Для этого нужно создать учетную запись на сайте https://hub.docker.com/ и присвоить метку образу в требуемом формате имя_пользователя/название_образа:версия
. Версию можно не указывать — в этом случае будет автоматически использовано latest
.
Чтобы назначить еще одну метку образу, выплоните команду:
docker tag my-app-bundle-image coolhacker/my-app-bundle-image
docker images | grep my-app-bundle
Теперь в спике образов видны два образа с одинаковым хэшем. На самом деле это один и тот же образ, но с двумя разными метками.
Тоже самое, если необходимо версионировать образы.
Для загрузки испльзуется команда:
docker push coolhacker/my-app-bundle-image
Если нужно загрузить все метки, которые присвоены указанному образу, можно просто воспользоваться аргументом --all
.
Второй способ распространения — сохранение образа в tar. Это стандартный архив UNIX без сжатия. Чтобы сохранить образ в архив просто выполните команду:
docker save my-app-bundle-image -o my-app-bundle-image.tar
Вместо сохранения в файл можно вывести содержимое на стандартный вывод и перенаправить поток в gzip для сжатия.
Чтобы загрузить сохраненный таким способом образ на другой машине, скопируйте туда файл и выполните:
docker load -i my-app-bundle-image.tar
Вместо этого можно воспользоваться стандартный вводом, чтобы прочитать tar или сжатый архив.
Если вы распространяете образ с вашим приложением клиентам, но чтобы они имели возможность обновиться, нужно выполнить следующие действия:
docker stop my-app
docker rm my-app
dockrt pull coolhacker/my-app-bundle-image # если используется docker-hub
docker load -i my-app-bundle-image.tar # если используется распространение tarball
docker run -d --name=my-app-container \
--restart=always
-p 80:8080 \
-v $(pwd)/log:/logs \
-v /etc/localtime:/etc/localtime:ro \
-v /etc/timezone:/etc/timezone:ro \
my-app-bundle-image
Принцип теперь уже точно должен быть понятен.
В процессе эксплуатации возможно понадобиться подключиться к работающему контейнеру, чтобы что-то проверить. Это можно сделать при помощи команды:
docker exec -it my-app-container sh
Вызов этой команды приведет к тому, что в работающем контейнере через точку входа будет выполнена команда sh
а также будет подключен стандартный ввод/вывод и создан псевдо-TTY. В результате вы получите shell-доступ в контейнер, который выполняется. Можно посмотреть список процессов либо что угодно еще. Для отключения — Ctrl+C
.
Уничтожить контейнер можно при помощи команды:
docker stop my-app-container
docker rm my-add-container
Уничтожить метку образа можно при помощи команды:
docker rmi my-app-bundle-image
Если у образа больше не осталось меток, то будет уничтожен и сам образ.
Спасибо за внимание.