На Docker Hub выложено множество готовых образов, которые используются администраторами и разработчиками: интерпретаторы и компиляторы языков, веб-сервера, базы данных и многое другое. Большую часть из них можно использовать на серверах без изменений, передав какие-то переменные окружения. Но для любого разрабатываемого приложения нужно создавать свой собственный образ. В него войдет код приложения и все его зависимости. Даже когда нам будет нужно изменить всего лишь конфигурацию, например Nginx, все равно придется создать свой собственный образ, в который добавлен конфигурационный файл.
В этом уроке мы научимся создавать Docker-образ на примере JavaScript проекта: данный язык программирования достаточно распространён в среде разработчиков. Но все описанные принципы так же будут подходит и для других языков. Для создания образа будем использовать популярный микрофреймворк fastify.
Для начала создадим каркас приложения с помощью готового шаблона:
cd /var/tmp # можно выбрать любую директорию
mkdir docker-fastify-example
cd docker-fastify-example
docker run -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 создает образ на основе файла Dockerfile, в котором описываются необходимые команды. Мы начнем сразу с примера:
FROM node:18
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"}
Для полной проверки, откройте в браузере ссылку https://localhost:3000 и убедитесь что сайт открылся. Остался последний шаг - загрузить образ на Docker Hub. Для этого понадобится подготовительная работа:
docker login
в терминале. Docker попросит ввести имя пользователя и парольТеперь, чтобы загрузить образ в Docker Hub, мы должны дать ему правильное имя. По соглашению, часть имени Docker-образа до символа /, должна совпадать с именем вашего пользователя Docker Hub. Чтобы так сделать, вам необходимо запустить команду сборки еще раз:
docker build -t <имя вашего пользователя>/docker-fastify-example .
Теперь можно пушить:
docker build -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 состоит из команд, которые выполнятся сверху вниз по очереди, формируя файловую систему образа. Каждая последующая команда "видит" результаты предыдущей команды. Ниже мы разберем наиболее популярные команды, которые встречаются в большинстве образов.
# Варианты
# По умолчанию тег 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 /app
Команда WORKDIR
задает рабочий каталог, относительно которого выполняются все действия во время формирования образа и при входе в контейнер:
docker run -it hexlet/devops-fastify-app bash
root@02d29c66ea06:/app# # мы оказались внутри /app
WORKDIR
автоматически создаёт директорию, если её ещё нет.
# файлы
COPY package.json .
# Аналогично
# COPY package.json package.json
COPY package-lock.json .
# Копирование всех файлов внутрь
COPY . .
Команда COPY
копирует файлы и директории с хост-машины внутрь Docker-образа. Она принимает два параметра: первый - что копируем, второй - куда копируем и под каким именем. Второй параметр может принимать три варианта:
WORKDIR
Если точка идет первым параметром, то это обозначает что копироваться будет директория целиком.
Для полного понимания принципов работы команды COPY
, нужно представлять что такое контекст. Помните, когда мы указывали точку во время сборки образа? Это и есть контекст:
docker build -t hexlet/docker-fastify-example .
Контекст - это директория, относительно которой работает первый параметр в COPY
. Обычно контекстом указывают ту директорию, которая содержит Dockerfile. Но это не обязательно, ведь контекстом может быть и другая директория:
# Указана директория уровнем выше
# Dockerfile должен лежать в текущей директории, из которой идет запуск
docker build -t something ..
Во время сборки образа, контекст целиком копируется внутрь системных директорий Docker, из которых в образ переносится все, что указано в команде COPY
. Из-за этого иногда возникают проблемы. Контекст может содержать директории, которые не должны попадать в образ, например, .git
, или зависимости установленные локально (node_modules), так как они все равно устанавливаются заново во время сборки. Чтобы избежать их попадания во внутрь, нужно создать файл .dockerignore и указать там те директории и файлы, которые не должны быть частью контекста. Принцип работы файла такой же, как и у .gitignore.
node_modules
.git
logs
tmp
Игнорирвание таких директорий и файлов дает дополнительный плюс. Чем меньше размер контекста, тем быстрее он копируется. Если не следить за его размером, то процесс копирования может увеличиться до десятков секунд и даже минут.
# Если базовый образ 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
docker run -it hexlet/docker-fastify-example # npm start
# CMD не используется, так как явно указан bash
docker run -it hexlet/docker-fastify-example bash
ENV FASTIFY_ADDRESS 0.0.0.0
ENV VERSION 1
Задает переменные окружения. Команды, выполняющиеся после ENV
, видят эти переменные и могут их использовать.
С этой командой нужно быть острожнее. Переменные окружения созданы для того, чтобы их можно было менять, а их указание в Dockerfile фиксирует значения. По этому случаю, в Dockerfile обычно указывают только те переменные окружения, которые не зависят от среды запуска, как в примере выше. Нам в любом случае надо указать что сервер должен запускаться на 0.0.0.0 иначе его будет невозможно увидеть снаружи. В большинстве же ситуаций, переменные окружения передаются снаружи для конкретного запуска:
docker run -it -p 3000:3000 -e NODE_ENV=production hexlet/docker-fastify-example
Приложение devops-example-app использует переменную окружения SERVER_MESSAGE
для вывода части приветствия на страницу.
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт