Подготовка собственного образа — Docker: Основы
На Docker Hub выложено множество готовых образов, которые используются администраторами и разработчиками: интерпретаторы и компиляторы языков, веб-сервера, базы данных и многое другое. Большую часть из них можно использовать на серверах без изменений, передав какие-то переменные окружения. Но для любого разрабатываемого приложения нужно создавать свой собственный образ. В него войдет код приложения и все его зависимости. Даже когда нам будет нужно изменить всего лишь конфигурацию, например Nginx, все равно придется создать свой собственный образ, в который добавлен конфигурационный файл.
В этом уроке мы научимся создавать Docker-образ на примере JavaScript проекта: данный язык программирования достаточно распространен в среде разработчиков. Но все описанные принципы так же будут подходить и для других языков. Для создания образа будем использовать популярный микрофреймворк fastify .
Для начала создадим каркас приложения с помощью готового шаблона:
cd /var/tmp # можно выбрать любую директорию mkdir docker-fastify-example cd docker-fastify-example docker run --user $(id -u) -it -w /out -v `pwd`:/out node npm init fastify Need to install the following packages: create-fastify Ok to proceed? (y) y # введите y generated .gitignore generated README.md generated app.js generated .vscode/launch.json generated plugins/README.md generated routes/root.js generated test/helper.js generated plugins/sensible.js generated plugins/support.js generated routes/README.md generated routes/example/index.js generated test/routes/root.test.js generated test/plugins/support.test.js generated test/routes/example.test.js --> project example generated successfully run 'npm install' to install the dependencies run 'npm start' to start the application run 'npm run dev' to start the application with pino-colada pretty logging (not suitable for production) run 'npm test' to execute the unit tests
Эта команда создаст шаблон приложения в директории /out запущенного контейнера, которая, на самом деле, является директорией /var/tmp/docker-fastify-example на нашей машине. В итоге у нас получается такая структура проекта:
. # docker-fastify-example ├── README.md ├── app.js ├── package.json ├── plugins ├── routes └── test
Для запуска этого приложения, нам нужно выполнить две основные задачи: установить зависимости и запустить сервер. Без Docker это выглядит так:
# Если не стоит npm, # то сюда еще входит установка Node.js npm install npm start # или npm run dev в режиме разработки
Установку зависимостей нужно выполнить еще до создания образа, так как во время первой установки формируется файл package-lock.json. Он нужен для фиксации зависимостей: с его помощью мы гарантируем, что в образе будут использоваться ровно те зависимости, которые мы подключали во время разработки. Сделать это можно следующим образом:
# внутри директории docker-fastify-example docker run -it -w /out -v `pwd`:/out node npm install added 398 packages, and audited 560 packages in 45s
Теперь директория с приложением выглядит так:
. ├── README.md ├── app.js ├── node_modules # тут хранятся зависимости ├── package-lock.json # новый файл ├── package.json ├── plugins ├── routes └── test
Сборка и публикация Docker-образа
Docker создает образ на основе файла Dockerfile, в котором описываются необходимые команды. Мы начнем сразу с примера:
FROM node:20 WORKDIR /app COPY package.json . COPY package-lock.json . RUN npm ci COPY . . ENV FASTIFY_ADDRESS 0.0.0.0 # Команда, которая запускается автоматически # при старте контейнера CMD ["npm", "start"]
В основном, команды Dockerfile интуитивно понятны. Видно, что мы «упаковываем» приложение в образ, выполняем установку зависимостей и описываем то, как его запустить. Подробнее о командах мы поговорим позже, а сейчас посмотрим, как собирается, запускается и пушится образ в Docker Hub.
Для сборки образа в директории с Dockerfile нужно выполнить команду указанную ниже:
# -t, --tag - имя образа и тега. По умолчанию latest # Точка в конце важна, подробнее про нее дальше docker build -t hexlet/docker-fastify-example . [+] Building 26.4s (12/12) FINISHED => [internal] load build definition from Dockerfile => => transferring dockerfile: 190B => [internal] load .dockerignore => => transferring context: 2B => [internal] load metadata for docker.io/library/node:18 => [auth] library/node:pull token for registry-1.docker.io => [internal] load build context => => transferring context: 63.29MB => [1/6] FROM docker.io/library/node:18@sha256:e5b7b3 => [2/6] WORKDIR /app => [3/6] COPY package.json . => [4/6] COPY package-lock.json . => [5/6] RUN npm ci => [6/6] COPY . . => exporting to image => => exporting layers => => writing image sha256:52f6fe => => naming to docker.io/library/docker-fastify-example
Сборка образа занимает какое-то время: нужно подождать, пока выполнятся все команды. Как результат, в списке образов появляется образ с именем hexlet/docker-fastify-example и тегом latest. Его можно запустить и убедиться в работоспособности:
# По умолчанию Fastify стартует на 3000 порту # Docker запускает команду npm start docker run -it -p 3000:3000 hexlet/docker-fastify-example "level":30,"time":1651503036761,"pid":22,"hostname":"a9b1ea7fc320","msg":"Server listening at http://0.0.0.0:3000">
Для полной проверки, откройте в браузере ссылку http://localhost:3000 и убедитесь что сайт открылся. Остался последний шаг — загрузить образ на Docker Hub. Для этого понадобится подготовительная работа:
- Регистрация https://hub.docker.com/
- Подключение к аккаунту через запуск команды docker login в терминале. Docker попросит ввести имя пользователя и пароль
- Создание репозитория с именем docker-fastify-example в личном кабинете
Теперь, чтобы загрузить образ в Docker Hub, мы должны дать ему правильное имя. По соглашению, часть имени Docker-образа до символа /, должна совпадать с именем вашего пользователя Docker Hub. Чтобы так сделать, вам необходимо запустить команду сборки еще раз:
-t /docker-fastify-example .
Теперь можно пушить:
-t /docker-fastify-example . # По умолчанию отправляется тег latest docker push /docker-fastify-example
Если репозиторий публичный, то скачать и запустить этот образ сможет любой человек, с доступом в интернет.
Теги
Теги у Docker-репозиториев изменяемые. Если изменить образ и снова его запушить с тем же тегом, образ поменяется. Для тега latest это ожидаемое поведение, а вот для версий нет. За этим нужно следить самостоятельно и не менять образ для уже существующих тегов. Если меняется образ, то правильно создавать новый тег:
# Используем тег docker build -t /docker-fastify-example:v2 . docker push /docker-fastify-example:v2
Команды Dockerfile
Dockerfile состоит из команд, которые выполнятся сверху вниз по очереди, формируя файловую систему образа. Каждая последующая команда «видит» результаты предыдущей команды. Ниже мы разберем наиболее популярные команды, которые встречаются в большинстве образов.
FROM
# Варианты # По умолчанию тег latest FROM ubuntu # С явно указанным тегом FROM node:18
Образ — это в первую очередь файловая система, которая формируется на базе команд описанных в Dockerfile. Docker берет какую-то первоначальную файловую систему и затем изменяет ее в соответствии с описанием. Получившаяся структура файлов и становится образом. Откуда берется первоначальная файловая система?
Практически все образы в Docker формируются не с нуля, а на базе уже существующих образов. Образы формируют дерево, в котором одни образы наследуют файловые системы других образов начиная с базового образа scratch .
# Иерархия образов docker-fastify-example FROM node FROM buildpack-deps:bullseye FROM buildpack-deps:bullseye-scm FROM buildpack-deps:bullseye-curl FROM debian:bullseye FROM scratch
Команда FROM задает образ, чья файловая система берется за основу. Все последующие команды, которые изменяют файловую систему, работают уже с ней. Потому команда FROM идет первой в Dockerfile.
WORKDIR
Команда WORKDIR задает рабочий каталог, относительно которого выполняются все действия во время формирования образа и при входе в контейнер:
-it hexlet/devops-fastify-app bash root@02d29c66ea06:/app# # мы оказались внутри /app
WORKDIR автоматически создает директорию, если ее еще нет.
COPY
# файлы COPY package.json . # Аналогично # COPY package.json package.json COPY package-lock.json . # Копирование всех файлов внутрь COPY . .
Команда COPY копирует файлы и директории с хост-машины внутрь Docker-образа. Она принимает два параметра: первый — что копируем, второй — куда копируем и под каким именем. Второй параметр может принимать три варианта:
- Абсолютный путь, копирование происходит ровно по нему
- Относительный путь, копирование происходит относительно установленной рабочей директории WORKDIR
- Точка, файл или директория копируется как есть в рабочую директорию
Если точка идет первым параметром, то это обозначает что копироваться будет директория целиком.
Для полного понимания принципов работы команды COPY , нужно представлять что такое контекст. Помните, когда мы указывали точку во время сборки образа? Это и есть контекст:
-t hexlet/docker-fastify-example .
Контекст — это директория, относительно которой работает первый параметр в COPY . Обычно контекстом указывают ту директорию, которая содержит Dockerfile. Но это не обязательно, ведь контекстом может быть и другая директория:
# Указана директория уровнем выше # Dockerfile должен лежать в текущей директории, из которой идет запуск docker build -t something ..
Во время сборки образа, контекст целиком копируется внутрь системных директорий Docker, из которых в образ переносится все, что указано в команде COPY . Из-за этого иногда возникают проблемы. Контекст может содержать директории, которые не должны попадать в образ, например, .git , или зависимости установленные локально (node_modules), так как они все равно устанавливаются заново во время сборки. Чтобы избежать их попадания во внутрь, нужно создать файл .dockerignore и указать там те директории и файлы, которые не должны быть частью контекста. Принцип работы файла такой же, как и у .gitignore.
Игнорирование таких директорий и файлов дает дополнительный плюс. Чем меньше размер контекста, тем быстрее он копируется. Если не следить за его размером, то процесс копирования может увеличиться до десятков секунд и даже минут.
RUN
# Если базовый образ Ubuntu, то доступен apt RUN apt-get update && apt-get install -q curl RUN npm install
Команда RUN выполняет переданную строчку в терминале от пользователя root. С ее помощью вносятся основные изменения в файловую систему, добавляются пакеты, ставятся зависимости и так далее. Команд RUN может быть добавлено любое количество, обычно делают по одной команде на одно действие.
RUN выполняется в не интерактивном режиме, это значит, что если выполняемая команда запросит пользовательский ввод, например разрешение на установку чего-либо, то мы не сможем выбрать ответ yes. Поэтому все команды в RUN запускают в неинтерактивном режиме:
# -q - ставить автоматически не задавая вопросов RUN apt-get install -q curl
CMD
CMD задает команду, которая выполняется при запуске контейнера по умолчанию. Она используется только в том случае, если контейнер был запущен без указания команды
# Используется CMD docker run -it hexlet/docker-fastify-example # npm start # CMD не используется, так как явно указан bash docker run -it hexlet/docker-fastify-example bash
ENV
Задает переменные окружения. Команды, выполняющиеся после ENV , видят эти переменные и могут их использовать.
С этой командой нужно быть острожнее. Переменные окружения созданы для того, чтобы их можно было менять, а их указание в Dockerfile фиксирует значения. По этому случаю, в Dockerfile обычно указывают только те переменные окружения, которые не зависят от среды запуска, как в примере выше. Нам в любом случае надо указать, что сервер должен запускаться на 0.0.0.0, иначе его будет невозможно увидеть снаружи. В большинстве ситуаций переменные окружения передаются снаружи для конкретного запуска:
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Как создавать образы Docker с помощью Dockerfile
Образ Docker — это план контейнеров Docker, который содержит приложение и все необходимое для его запуска. Контейнер — это исполняемый экземпляр изображения.
В этом руководстве мы объясним, что такое Dockerfile, как его создать и как создать образ Docker с помощью Dockerfile.
Что такое Dockerfile
Dockerfile — это текстовый файл, содержащий все команды, которые пользователь может запустить в командной строке для создания образа. Он включает в себя все инструкции, необходимые Docker для создания образа.
Образы Docker состоят из серии слоев файловой системы, представляющих инструкции в файле Dockerfile образа, составляющем исполняемое программное приложение.
Файл Docker имеет следующую форму:
# Comment INSTRUCTION arguments
INSTRUCTION не чувствительна к регистру, но по соглашению для ее имен используется ЗАПИСЬ.
Ниже приведен список с кратким описанием некоторых из наиболее часто используемых инструкций Dockerfile:
- ARG — эта инструкция позволяет вам определять переменные, которые могут быть переданы во время сборки. Вы также можете установить значение по умолчанию.
- FROM — базовое изображение для построения нового изображения. Эта инструкция должна быть первой инструкцией без комментариев в Dockerfile. Единственное исключение из этого правила — когда вы хотите использовать переменную в аргументе FROM . В этом случае FROM может предшествовать одна или несколько инструкций ARG .
- LABEL — используется для добавления метаданных к изображению, таких как описание, версия, автор и т. Д. Вы можете указать несколько LABEL , и каждая инструкция LABEL представляет собой пару «ключ-значение».
- RUN — команды, указанные в этой инструкции, будут выполняться в процессе сборки. Каждая инструкция RUN создает новый слой поверх текущего изображения.
- ДОБАВИТЬ — используется для копирования файлов и каталогов из указанного источника в указанное место назначения в образе докера. Источником могут быть локальные файлы, каталоги или URL. Если источником является локальный tar-архив, он автоматически распаковывается в образ Docker.
- КОПИРОВАТЬ — аналогично ADD но источником может быть только локальный файл или каталог.
- ENV — Эта инструкция позволяет вам определить переменную среды.
- CMD — используется для указания команды, которая будет выполняться при запуске контейнера. Вы можете использовать только одну инструкцию CMD в своем Dockerfile.
- ENTRYPOINT — аналогично CMD , эта инструкция определяет, какая команда будет выполняться при запуске контейнера.
- WORKDIR — эта директива устанавливает текущий рабочий каталог для инструкций RUN , CMD , ENTRYPOINT , COPY и ADD .
- ПОЛЬЗОВАТЕЛЬ — Установите имя пользователя или UID для использования при выполнении любых следующих инструкций RUN , CMD , ENTRYPOINT , COPY и ADD .
- VOLUME — позволяет подключить каталог хост-машины к контейнеру.
- EXPOSE — используется для указания порта, на котором контейнер прослушивает во время выполнения.
Чтобы исключить добавление файлов и каталогов в образ, создайте файл .dockerignore в контекстном каталоге. Синтаксис .dockerignore аналогичен .gitignore файла .gitignore Git.
Для получения полной справки и подробного объяснения инструкций Dockerfile см. Официальную справочную страницу Dockerfile .
Создать Dockerfile
Наиболее распространенный сценарий при создании образов Docker — извлечь существующий образ из реестра (обычно из Docker Hub) и указать изменения, которые вы хотите внести в базовый образ. Чаще всего при создании образов Docker используется базовый образ Alpine, поскольку он небольшой и оптимизирован для работы в ОЗУ.
Docker Hub — это облачная служба реестра, которая, помимо прочего, используется для хранения образов Docker в общедоступном или частном репозитории.
В этом примере мы создадим образ Docker для сервера Redis. Мы будем использовать последнюю версию ubuntu 18.04 в качестве базового образа.
Сначала создайте каталог, который будет содержать Dockerfile и все необходимые файлы:
mkdir ~/redis_docker
Перейдите в каталог и создайте следующий Dockerfile:
cd ~/redis_dockernano Dockerfile
Dockerfile
FROM ubuntu:18.04 RUN apt-get update && apt-get install -y redis-server && apt-get clean EXPOSE 6379 CMD ["redis-server", "--protected-mode no"]
Давайте объясним значение каждой из строк в Dockerfile:
- В строке 1 мы определяем базовое изображение.
- Команда RUN которая начинается в строке 3 , обновит индекс apt, установит пакет «redis-server» и очистит кеш apt. Команды, используемые в инструкциях, совпадают с командами, которые вы использовали бы для установки Redis на сервере Ubuntu .
- Инструкция EXPOSE определяет порт, который прослушивает сервер Redis.
- В последней строке мы используем инструкцию CMD для установки команды по умолчанию, которая будет выполняться при запуске контейнера.
Сохраните файл и закройте редактор.
Создание образа
Следующим шагом будет создание образа. Для этого выполните следующую команду из каталога, в котором находится Dockerfile:
docker build -t linuxize/redis .
Параметр -t указывает имя изображения и, необязательно, имя пользователя и тег в формате «имя пользователя / воображаемое имя: тег».
Результат процесса сборки будет выглядеть примерно так:
Sending build context to Docker daemon 3.584kB Step 1/4 : FROM ubuntu:18.04 ---> 7698f282e524 Step 2/4 : RUN apt-get update && apt-get install -y gosu redis-server && apt-get clean ---> Running in e80d4dd69263 . Removing intermediate container e80d4dd69263 ---> e19fb7653fca Step 3/4 : EXPOSE 6379 ---> Running in 8b2a45f457cc Removing intermediate container 8b2a45f457cc ---> 13b92565c201 Step 4/4 : CMD ["redis-server", "--protected-mode no"] ---> Running in a67ec50c7048 Removing intermediate container a67ec50c7048 ---> d8acc14d9b6b Successfully built d8acc14d9b6b Successfully tagged linuxize/redis:latest
Когда процесс сборки завершится, новый образ появится в списке образов:
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE linuxize/redis latest d8acc14d9b6b 4 minutes ago 100MB ubuntu 18.04 7698f282e524 5 days ago 69.9MB
Если вы хотите отправить образ в Docker Hub, см. « Отправка образа контейнера Docker в Docker Hub» .
Запуск контейнера
Теперь, когда образ создан, вы запускаете из него контейнер, запустив:
docker run -d -p 6379:6379 --name redis linuxize/redis
Параметры -d указывают Docker, что нужно запустить контейнер в автономном режиме, параметр -p 6379:6379 опубликует порт 6379 на хост-машине, а параметр —name redis указывает имя контейнера. Последний аргумент linuxize/redis — это имя изображения, которое используется для запуска контейнера.
Когда контейнер запускается, используйте следующую команду для вывода списка всех запущенных контейнеров :
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6b7d424cd915 linuxize/redis:v0.0.1 "redis-server '--pro…" 5 minutes ago Up 5 minutes 0.0.0.0:6379->6379/tcp redis
Чтобы убедиться, что все работает так, как следует, используйте redis-cli для подключения к контейнеру докера:
redis-cli ping
Сервер Redis должен ответить PONG .
Выводы
В этом руководстве были рассмотрены только основы использования Dockerfiles для создания образов. Чтобы узнать больше о том, как писать файлы Docker, и рекомендуемые передовые методы, см. Рекомендации по написанию файлов Docker .
Если у вас есть вопросы, оставьте комментарий ниже.
Работа с Dockerfile

Приведем пример несложного Dockerfile, и на его примере разберем логику взаимодействия инструкций между собой.
FROM python:latest
RUN apt-get update && apt-get install python3-pip -y && pip install —upgrade pip && pip install pipenv
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app/
COPY . /usr/src/app/
EXPOSE 5000
RUN pip install —no-cache-dir -r requirements.txt
CMD [«python», «web_interface.py»]
Наш Dockerfile, как и положено, открывается инструкцией FROM с указанием родительского образа. Выбрать подходящий образ можно на hub.docker.com или с помощью команды docker search имя образа . Например, docker search python .
Далее следуют несколько команд RUN . Поскольку каждая инструкция создает новый слой — хорошей практикой считается писать конвейеры логически связанных команд в одной инструкции RUN . Например, мы поместили команды обновления пакетов apt , установку пакетного менеджера pip и его обновление в одну инструкцию. Для комбинирования команд используется логический оператор «И», обозначающийся как && .
После серии инструкций RUN идет инструкция WORKDIR , которая устанавливает рабочую директорию контейнера. Все последующие команды, работающие с привязками к директории, такие как RUN , CMD , ENTRYPOINT будут выполняться исходя из установленной директории.
Далее идёт инструкция EXPOSE , которая выражает намерение открыть заданный порт. Инструкция сама по себе не открывает порт без применения команды docker run с ключом -P. Если нужно «повесить» контейнер на определённый внешний порт с переадресацией во внутренний порт контейнера — применяется ключ -p с указанием внутреннего и внешнего порта через «:». Например, docker run -p 5000:8080 .
Предпоследней идет опять инструкция RUN . Мы уже рассматривали её выше, однако здесь она исполняется с зависимостью от WORKDIR . Инструкция WORKDIR в качестве рабочей директории установила /usr/src/app/ , поэтому текущая инструкция RUN будет выполнять команду pip install —no-cache-dir -r requirements.txt из директории установленной инструкцией WORKDIR .
Завершающая инструкция в нашем Dockerfile — CMD с командой [«python», «web_interface.py»] . Опять же заметим, что здесь CMD связана с WORKDIR и скрипт web_interface.py будет выполнен из директории /usr/src/app/ . Важно запомнить, что CMD всегда идет последней и должна быть в единственном экземпляре.
Приведем таблицу инструкций Dockerfile с примерами команд и опций:
| Инструкция | Описание | Пример использования | Комментарий |
|---|---|---|---|
| FROM | Задает базовый образ. Все последующие инструкции создают слои поверх родительского образа. | FROM python:latest FROM debian:wheezy |
Быстрее всего можно найти образ с нужным тегом на Docker hub. |
| RUN | Выполняет команду внутри контейнера и сохраняет результат. | RUN mkdir /usr/src/app/ RUN apt-get update && apt-get install python3-pip -y |
RUN может исполнять конвейер команд с логическими операторами && и ||. |
| COPY | Копирует файлы и папки из текущей директории, где находится пользователь в указанную директорию в контейнере | COPY . /usr/src/app/ | COPY считывает позицию пользователя на хосте, поэтому первым аргументом идет «.». |
| ADD | Копирует файлы и папки из текущей позиции пользователя, скачивает файлы по URL и работает с tar-архивами. | ADD https://1cloud.ru/archive/api_config.ini /usr/src/app/ |
Официальная документация не рекомендует применять ADD. Для скачивания по URL можно использовать RUN с CURL или WGET, а для копирования — COPY. |
| CMD | Выполняет команду с указанными аргументами во время запуска контейнера. | CMD [«python», «web_interface.py»] | CMD должна быть одна в конце Dockerfile. CMD может вызывать исполняемый файл — .sh Аргументы docker run переопределяют CMD. Если в Dockerfile нет CMD, обязательно должна быть инструкция ENTRYPOINT. |
| ENTRYPOINT | Похожа на CMD, но при запуске контейнера не переопределяется в отличие от CMD. | ENTRYPOINT [«python», «web_interface.py»] | ENTRYPOINT может использоваться совместно с CMD. |
| ENV | Задает переменные среды внутри образа, на которые могут ссылаться другие инструкции. | ENV ADMIN=»ivan» | ENV часто применяется для передачи информации в контейнеризированное приложение через переменные среды. |
| ARG | Задает переменные, значение которых передается докером во время сборки образа. | ARG maintainer=ivan | В отличие от ENV-переменных, ARG-переменные недоступны во время выполнения контейнера. |
| WORKDIR | Устанавливает рабочую директорию контейнера. | WORKDIR /usr/src/app/ | Последующие инструкции CMD, RUN, ENTRYPOINT наследуют привязку к директории установленной WORKDIR. |
| VOLUME | Создает и подключает постоянный том хранения данных. | VOLUME /data_cont_1 | Просмотреть существующие тома можно командой docker volume ls. К контейнеру можно подключить существующий том, для этого достаточно указать уже существующий том. |
| EXPOSE | Указывает планируемый рабочий порт у контейнера. Инструкция сама по себе не открывает порт. Чтобы использовался указанный в EXPOSE порт — нужно указать docker run -P при запуске контейнера. | EXPOSE 5000 | Если требуется пробросить и сопоставить разные порты внутри и снаружи контейнера используется docker run -p внутренний порт:внешний порт |
| LABEL | Добавляет метаданные в образ. | LABEL maintainer=»katkov_ivan» | Обычно LABEL содержит информацию об авторе образа. |
Если вы интересуетесь Docker и контейнеризацией в целом, вам могут быть интересны следующие материалы:

История контейнеризации
Краткая история контейнеризации и разбор конкретных технологий: chroot, jail, namespaces и cgroups.
Как развернуть свое Docker хранилище в Ubuntu 20.04
В данном руководстве мы изучим, как развернуть своё собственное Docker хранилище на сервере, работающем под управлением Ubuntu 20.04.
Хранилище Docker представляет из себя приложение, которое управляет хранением и доставкой образов контейнеров Docker. Хранилища собирают образа контейнеров и снижают время сборки для разработчиков. Образы Docker обеспечивают создание одной и той же среды выполнения благодаря виртуализации, при том, что создание образа может потребовать значительных временных затрат. Другими словами, разработчики имеют возможность загрузить из хранилища сжатый образ, содержащий все необходимые компоненты, вместо того, чтобы устанавливать зависимости и пакеты отдельно для использования Docker.
У Docker есть бесплатное общедоступное хранилище, Docker Hub, которое может припарковать ваши образы Docker. Тонкость состоит в том, что не всегда есть желание хранить свои образы там, где они будут общедоступны. Образы обычно содержат весь необходимый для запуска код, поэтому использование собственного реестра предпочтительнее для хранения проприентарного программного обеспечения.
Мы будем использовать Docker Compose, чтобы определять конфигурации запуска ваших контейнеров Docker. А также, мы будем использовать веб-сервер Nginx для передачи трафика сервера из интернета в работающий контейнер Docker. Изучив содержимое данного руководства, вы сможете поместить образ Docker в своё личное хранилище, а также, безопасно извлечь его с удалённого сервера.
Подготовка серверов
В данном мануале мы будем использовать два сервера. Один из них будет выступать в качестве хоста для вашего приватного Docker хранилища, а другой – в качестве сервера-клиента. Оба VPS работают под управлением операционной системы Ubuntu 20.04. Все работы на серверах мы будем производить под учётной записью, не являющейся root-ом, но имеющей привилегии sudo. Для настройки межсетевого экрана на серверах мы будем использовать интерфейс UFW.
Так как в качестве веб-сервера мы планируем использовать Nginx, то давайте перейдём к его настройке. Действия по настройке Nginx нужно будет произвести на сервере, выступающем в качестве хоста для нашего хранилища. Сначала необходимо установить Nginx:
$ sudo apt update $ sudo apt install nginx
Теперь добавьте Nginx в список приложений, доступ для которых разрешён в нашем брандмауэре, и проверьте статус службы Nginx:
$ sudo ufw allow 'Nginx HTTP' $ systemctl status nginx

Если сервис запущен вы можете проверить его доступность через веб-интерфейс. Для чего в браузере наберите IP-адрес вашего сервера-хоста:

Далее, вам необходимо будет добавить виртуальный хост для вашего домена. В нашем мануале мы будем использовать домен my-domain.host . Обратите внимание, что А-запись домена, с которым вы будете производить дальнейшие действия, должна соответствовать IP-адресу вашего сервера-хоста.
Итак, создайте каталог для вашего сайта и предоставьте ему соответствующие права:
$ sudo mkdir -p /var/www/my-domain.host/html $ sudo chown -R $USER:$USER /var/www/my-domain.host/html $ sudo chmod -R 755 /var/www/my-domain.host
Теперь в созданном каталоге создайте файл страницы сайта index.html :
$ sudo nano /var/www/my-domain.host/html/index.html
Наполните его содержимым:
Domain on Nginx Connection to my-domain.host is created successfully.
Закройте файл, сохранив изменения ( Ctrl+X , после чего Y и Enter ).
В директории /etc/nginx/sites-available/ создайте файл my-domain.host :
$ sudo nano /etc/nginx/sites-available/my-domain.host
Скопируйте в него следующие строки:
server < listen 80; listen [::]:80; root /var/www/my-domain.host/html; index index.html index.htm index.nginx-debian.html; server_name my-domain.host www.my-domain.host; location / < try_files $uri $uri/ =404; >>
Закройте файл с сохранением изменений и создайте ссылку в /etc/nginx/sites-enabled/ :
$ sudo ln -s /etc/nginx/sites-available/my-domain.host /etc/nginx/sites-enabled/
Отредактируйте файл /etc/nginx/nginx.conf :
$ sudo nano /etc/nginx/nginx.conf
В этом файле вам нужно будет раскомментировать строку, которая содержит server_names_hash_bucket_size .
Для проверки корректности синтаксиса Nginx наберите:
$ sudo nginx -t

$ sudo systemctl restart nginx
После этого ваш домен должен стать доступным по своему доменному имени:

И нам останется только настроить редирект всего трафика с протокола HTTP на HTTPS. Мы осуществим это при помощи центра сертификации Let’s Encrypt.
Сначала необходимо будет установить пакет snap:
$ sudo apt install snapd
Затем с его помощью проинсталлируйте Certbot:
$ sudo snap install --classic certbot $ sudo ln -s /snap/bin/certbot /usr/bin/certbot
Далее, в брандмауэре необходимо разрешить трафик по протоколу HTTPS, закрыв трафик по HTTP:
$ sudo ufw allow 'Nginx Full' $ sudo ufw delete allow 'Nginx HTTP' $ sudo ufw status

Теперь, необходимо запустить Certbot с помощью плагина nginx для указания доменов, которые будут использовать сертификаты:
$ sudo certbot --nginx
Здесь вам нужно будет указать свой E-mail, согласиться с предоставляемыми условиями и указать домен, для которого требуется активировать протокол HTTPS.
С этого момента подключение к вашему домену защищено:


Далее, переходим с настройке Docker.
Установка Docker
Пакет Docker будем устанавливать из официального репозитория Docker. Чтобы сделать это, мы добавим новый источник пакетов и ключ GPG от Docker, чтобы быть уверенными в валидности загрузки пакета. После чего установим пакет.
Проинсталлируйте несколько необходимых пакетов, которые позволят установщику использовать пакеты обновлений по протоколу HTTPS:
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
Теперь, добавьте в систему ключ GPG для официального репозитория Docker:
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
Далее, добавьте репозиторий Docker и запустите обновление базы данных пакетов обновлений:
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable" $ sudo apt update
Убедитесь, что дальнейшая установка будет произведена из репозитория Docker, а не из дефолтного репозитория Ubuntu:
$ apt-cache policy docker-ce

Обратите внимание, что docker-ce не установлен, но готов к установке из репозитория Ubuntu 20.04.
И наконец, запустите установку Docker:
$ sudo apt install docker-ce
Теперь Docker установлен, служба запущена и процесс доступен для старта при загрузке системы. Проверьте:
$ sudo systemctl status docker

Также, нам нужно будет установить инструмент, который позволяет осуществлять запуск сред выполнения приложений с несколькими контейнерами. Это – Docker Compose. Чтобы загрузить самую свежую Docker Compose, посмотрите её номер на странице официального репозитория Github. Наберите в командой строке команду, которая загрузит исполняемый файл:
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

После этого задайте загруженному файлу соответствующие права. Это позволит сделать docker-compose исполняемым файлом:
$ sudo chmod +x /usr/local/bin/docker-compose
Проверьте версию установленного компонента:
$ docker-compose --version

Теперь всё готово для установки и настройки Docker хранилища.
Установка и настройка Docker хранилища
В командной строке Docker очень полезен когда вы запускаете и тестируете контейнеры. Но для больших развёртываний, включающих несколько параллельно выполняющихся контейнеров, она оказывается очень громоздкой.
С помощью Docker Compose вы можете создать один файл .yml для настройки конфигурации каждого контейнера и данных, которые нужны контейнерам для взаимодействия друг с другом. Вы можете использовать docker-compose как инструмент командной строки для выдачи команд всем компонентам, составляющих ваше приложение, и управления ими как группой.
Docker хранилище само по себе является приложением с несколькими компонентами, поэтому для управления им вы будете использовать Docker Compose. Для запуска хранилища, необходимо будет создать файл docker-compose.yml , который определит как хранилище, так и расположение на диске, где оно будет хранить данные.
На сервере-хосте создайте каталог docker-registry для хранения настроек и перейдите в него:
$ mkdir ~/docker-registry $ cd ~/docker-registry
Там создайте каталог data , где будут храниться образы:
$ mkdir data
Создайте файл docker-compose.yml :
$ sudo nano docker-compose.yml
Добавьте в него следующие строки, которые определят основной экземпляр хранилища:
version: '3' services: registry: image: registry:2 ports: - "5000:5000" environment: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./data:/data
- registry – название первой службы;
- registry:2 – определение образа в registry версии 2 ;
- ports – сопоставление порта 5000 на хосте порту 5000 на контейнере;
- REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY – переменная, определяющая каталог для хранения данных;
- volumes – сопоставление каталога /data на сервере-хосте каталогу /data в контейнере.
Теперь запустите созданную конструкцию:
$ docker-compose up
Контейнер и его зависимости будут загружены и запущены:

Обратите внимание на сообщение No HTTP secret provided . К нему мы ещё вернёмся в данном руководстве. Последняя строка содержит сообщение о том, что запущено прослушивание на порту 5000 .
Чтобы прервать выполнение команды, нажмите Ctrl+C .
Настройка переадресации портов Nginx
Ранее, мы уже настроили доступ к нашему домену по протоколу HTTPS. Чтобы открыть защищённое хранилище, вам необходимо будет всего лишь настроить Nginx в части перенаправления трафика от домена к контейнеру хранилища.
Откройте для редактирования созданный ранее файл, содержащий настройки вашего сервера:
$ sudo nano /etc/nginx/sites-available/my-domain.host
Найдите там блок location :

Необходимо переадресовать трафик на порт 5000 , на котором хранилище будет слушать трафик. Также, можно добавить заголовки к запросам, направленным хранилищу, которое от имени сервера предоставляет дополнительную информацию о самом запросе. Замените содержимое блока location следующими строками:
location / < # Do not allow connections from docker 1.5 and earlier # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) < return 404; >proxy_pass http://localhost:5000; proxy_set_header Host $http_host; # required for docker client's sake proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 900; >
Блок if проверяет пользовательский агент запроса, верифицирует версию клиента Docker выше 1.5 и определяет, что это не приложение Go, которое пытается получить доступ.
Сохраните файл перед его закрытием. Для применения новых настроек перезапустите Nginx:
$ sudo systemctl restart nginx
Чтобы убедиться, что Nginx правильно перенаправляет трафик в контейнер хранилища на порту 5000 , запустите его:
$ cd ~/docker-registry $ docker-compose up
И теперь, наберите в браузере:
https://my-domain.host/v2
Вы увидите пустой объект JSON:

В терминале же вы сможете видеть, что в последней строке запрос GET был сделан в /v2/ . Это указывает на какую конечную точку вы отправили запрос из своего браузера. Другими словами, контейнер получил запрос, который вы сделали, и вернул ответ <> . Код http.response.status=200 в последних строках означает, что контейнер справился с запросом успешно.

Чтобы прервать выполнение команды, нажмите Ctrl+C .
Настройка аутентификации
Nginx позволяет настроить аутентификацию HTTP для сайтов, которыми Nginx управляет. Именно это вы можете использовать для ограничения доступа к вашему хранилищу Docker. Чтобы этого добиться, необходимо создать файл аутентификации при помощи htpasswd и добавить в него комбинацию пользователь-пароль.
Утилиту htpasswd вы можете получить, например, установив пакет apache2-utils :
$ sudo apt install apache2-utils
Теперь необходимо сохранить файл проверки подлинности с учетными данными в ~/docker-registry/auth/ :
$ mkdir ~/docker-registry/auth $ cd ~/docker-registry/auth
На следующем шаге создайте своего первого пользователя, заменив user1 на какое-нибудь своё имя учётной записи. Здесь, опция -B нужна для применения алгоритма bcrypt :
$ htpasswd -Bc registry.password user1
Далее, введите пароль для создаваемого пользователя:

Теперь учётная запись user1 и её пароль добавлены в registry.password .
Если вам необходимо добавить других пользователей, повторите предыдущую команду, но с другим именем пользователя и без использования флага -c , который отвечает за создание нового файла:
$ htpasswd -B registry.password user2
Теперь нужно заставить Docker использовать созданный файл аутентификации. Для этого отредактируйте его:
$ sudo nano ~/docker-registry/docker-compose.yml
Добавьте в файл выделенные строки:
version: '3' services: registry: image: registry:2 ports: - "5000:5000" environment: REGISTRY_AUTH: htpasswd REGISTRY_AUTH_HTPASSWD_REALM: Registry REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./auth:/auth - ./data:/data
Таким образом, мы добавили переменные окружения, указывающие на необходимость использования аутентификации HTTP, и указали путь к созданному файлу htpasswd . Для REGISTRY_AUTH мы указали значение htpasswd , которое аутентифицирует используемую схему. Переменная REGISTRY_AUTH_HTPASSWD_PATH содержит путь к файлу аутентификации. Переменная REGISTRY_AUTH_HTPASSWD_REALM означает имя области htpasswd .
Также, мы смонтировали каталог ./auth/ , чтобы сделать файл доступным внутри контейнера хранилища. Закройте файл сохранив внесённые изменения.
Теперь можно проверить корректность работы системы аутентификации:
$ cd ~/docker-registry $ docker-compose up
После этого, обновите страницу браузера, в котором открыт ваш домен. Система должна попросить вас ввести имя пользователя и пароль:

Введите учётные данные и, в случае успешной аутентификации, вы увидите пустой объект JSON:

Это означает, что вы успешно прошли проверку подлинности и получили доступ к хранилищу.
Как и раньше, выход осуществляется через Ctrl+C .
Теперь ваше хранилище защищено, и доступ к нему можно осуществить только после успешной проверки подлинности. Далее, мы настроим его на запуск в фоновом режиме, при этом хранилище будет устойчиво к перезагрузкам, запускаясь каждый раз автоматически.
Запуск Docker хранилища как службы
Для того, чтобы контейнер хранилища запускался каждый раз при загрузке системы или после её сбоя, необходимо проинструктировать Docker Compose всегда поддерживать его работу.
Отредактируйте файл docker-compose.yml :
$ sudo nano ~/docker-registry/docker-compose.yml
Добавьте в файл выделенную строку:
version: '3' services: registry: restart: always image: registry:2 ports: - "5000:5000" environment: REGISTRY_AUTH: htpasswd REGISTRY_AUTH_HTPASSWD_REALM: Registry REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data volumes: - ./auth:/auth - ./data:/data
Настройка restart гарантирует нам, что контейнер выдержит перезапуск системы. Сохраните изменения и закройте файл.
А теперь, запустите ваше хранилище как фоновый процесс при помощи опции -d :
$ docker-compose up -d
С этого момента ваше хранилище работает в фоновом режиме, и поэтому, вы можете спокойно закрыть сессию SSH, и даже перезагрузить сервер. Это не окажет на хранилище никакого эффекта.
Увеличение размера загружаемых файлов для Nginx
Перед тем, как помещать образ в хранилище, необходимо убедиться, что наше хранилище сможет обрабатывать загрузку файлов большого объёма.
По умолчанию предельный размер загружаемого файла в Nginx составляет 1MB. Этого явно не достаточно для образа Docker. Чтобы это исправить, необходимо внести изменения в главный конфигурационный файл Nginx. Он расположен в директории /etc/nginx/ . Откройте его для редактирования, набрав в командной строке:
$ cd /etc/nginx $ sudo nano nginx.conf
Найдите там секцию http и добавьте в неё выделенную строку:
http < ## # Basic Settings ## client_max_body_size 16384m; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048;
Значение параметра client_max_body_size теперь установлено в 16384 MB. Это означает, что максимальный размер загружаемого файла равно 16GB.
Закройте файл с сохранением изменений и перезапустите Nginx:
$ sudo systemctl restart nginx
Публикация в Docker хранилище
Настало время попробовать загрузить образ на наше хранилище. Так как у нас пока нет доступных образов, мы можем, в качестве теста, использовать образ ubuntu , который можно скачать с общедоступного Docker хранилища.
Действия по публикации образа в наше хранилище мы будем производить на нашем втором сервере, который мы обозначили как сервер-клиент.
Для начала, чтобы на этом сервере запускать команду docker не используя полномочия sudo , необходимо добавить вашего пользователя в группу docker :
$ sudo usermod -aG docker your-user
Теперь, для того, чтобы загрузить образ ubuntu , запустить его и получить доступ к его оболочке, в командной строке вашего сервера-клиента наберите:
$ docker run -t -i ubuntu /bin/bash
Опции -i и -t позволяют вам получить интерактивный доступ к оболочке внутри контейнера.
Теперь, когда вы подключились к оболочке, в корне системы создайте файл my.IMAGE :
root@2b8dcf17a0db:/# touch /my.IMAGE
Создав такой файл, мы изменили первоначальный контейнер. Наличие этого файла позже позволит нам понять, что мы имеем дело именно с точно таким же контейнером.
Выход из оболочки контейнера осуществляется при помощи CNTL D , либо командой:
root@2b8dcf17a0db:/# exit
Далее, создайте новый образ из контейнера, в который вы только что внесли изменения:
$ docker commit $(docker ps -lq) new-ubuntu-image

Новый образ теперь доступен локально, и, значит, вы можете передать его в ваше хранилище. Сначала подключитесь к нему:
$ docker login https://my-domain.host
Введите имя пользователя и пароль для получения доступа к контейнеру. В нашем случае, это данные учётной записи user1 .
В случае успешной аутентификации система выведет подобное сообщение:

Теперь, когда вы подключились, переименуйте созданный образ:
$ docker tag new-ubuntu-image my-domain.host/new-ubuntu-image
И наконец, передайте только что отмеченный образ в ваше хранилище:
$ docker push my-domain.host/new-ubuntu-image
Вы получите вывод, аналогичный следующему:

После того, как мы отправили образ в хранилище, давайте лишние образы удалим. Сначала необходимо вывести на экран список локально доступных образов:
$ docker images

Мы удалим все образы, кроме первоначально загруженного образа ubuntu :
$ docker rmi new-ubuntu-image $ docker rmi my-domain.host/new-ubuntu-image
Если ещё раз вывести список образов, то вы увидите, что в списке остался только образ ubuntu :
$ docker images

Таким образом, мы проверили, как хранилище обрабатывает аутентификацию пользователей при их подключении и как позволяет пользователям, прошедшим проверку, передавать образ в хранилище. Теперь попробуем забрать образ из нашего хранилища.
Извлечение из Docker хранилища
Для загрузки образа из хранилища подключитесь к нему набрав на вашем сервере-клиенте команду:
$ docker login https://my-domain.host
Не забывайте использовать домен своего сервера-хоста вместо my-domain.host .
Для того, чтобы принять файл образа new-ubuntu-image , выполните следующую команду:
$ docker pull my-domain.host/new-ubuntu-image
После того, как образ загрузится, выполните команду для просмотра списка имеющихся на сервере-клиенте образов:
$ docker images

Здесь вы видите, что образ new-ubuntu-image снова присутствует на сервере-клиенте. Значит, к нему можно подключиться:
$ docker run -t -i my-domain.host/new-ubuntu-image
Подключившись, посмотрите содержимое корня системы:
root@65c69421278c:/# ls -l

Увидев файл my.IMAGE , можно сделать вывод, что это – экземпляр именно того образа, в который мы вносили коррективы перед загрузкой в наше хранилище.
Закрыть оболочку можно командой exit , либо нажав Ctrl+D .
Заключение
В данном руководстве мы увидели, как развернуть своё приватное Docker хранилище на сервере, работающем под управлением Ubuntu 20.04. Мы защитили подключение к развёрнутому хранилищу при помощи системы проверки подлинности учётных записей. А также, мы попробовали создать контейнер с образом Docker, передать его в хранилище и снова загрузить на сервер, использовавшийся нами как клиент нашего Docker хранилища.