0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>На Docker Hub выложено множество готовых образов, которые используются администраторами и разработчиками: интерпретаторы и компиляторы языков, веб-сервера, базы данных и многое другое. Большую часть из них можно использовать на серверах без изменений, передав какие-то переменные окружения. Но для любого разрабатываемого приложения нужно создавать свой собственный образ. В него войдет код приложения и все его зависимости. Даже когда нам будет нужно изменить всего лишь конфигурацию, например Nginx, все равно придется создать свой собственный образ, в который добавлен конфигурационный файл.</p>
1
<p>На Docker Hub выложено множество готовых образов, которые используются администраторами и разработчиками: интерпретаторы и компиляторы языков, веб-сервера, базы данных и многое другое. Большую часть из них можно использовать на серверах без изменений, передав какие-то переменные окружения. Но для любого разрабатываемого приложения нужно создавать свой собственный образ. В него войдет код приложения и все его зависимости. Даже когда нам будет нужно изменить всего лишь конфигурацию, например Nginx, все равно придется создать свой собственный образ, в который добавлен конфигурационный файл.</p>
2
<p>В этом уроке мы научимся создавать Docker-образ на примере JavaScript проекта: данный язык программирования достаточно распространен в среде разработчиков. Но все описанные принципы так же будут подходить и для других языков. Для создания образа будем использовать популярный микрофреймворк<a>fastify</a>.</p>
2
<p>В этом уроке мы научимся создавать Docker-образ на примере JavaScript проекта: данный язык программирования достаточно распространен в среде разработчиков. Но все описанные принципы так же будут подходить и для других языков. Для создания образа будем использовать популярный микрофреймворк<a>fastify</a>.</p>
3
<p>Для начала создадим каркас приложения с помощью готового шаблона:</p>
3
<p>Для начала создадим каркас приложения с помощью готового шаблона:</p>
4
<p>Эта команда создаст шаблон приложения в директории<em>/out</em>запущенного контейнера, которая, на самом деле, является директорией<em>/var/tmp/docker-fastify-example</em>на нашей машине. В итоге у нас получается такая структура проекта:</p>
4
<p>Эта команда создаст шаблон приложения в директории<em>/out</em>запущенного контейнера, которая, на самом деле, является директорией<em>/var/tmp/docker-fastify-example</em>на нашей машине. В итоге у нас получается такая структура проекта:</p>
5
<p>Для запуска этого приложения, нам нужно выполнить две основные задачи: установить зависимости и запустить сервер. Без Docker это выглядит так:</p>
5
<p>Для запуска этого приложения, нам нужно выполнить две основные задачи: установить зависимости и запустить сервер. Без Docker это выглядит так:</p>
6
<p>Установку зависимостей нужно выполнить еще до создания образа, так как во время первой установки формируется файл<em>package-lock.json</em>. Он нужен для фиксации зависимостей: с его помощью мы гарантируем, что в образе будут использоваться ровно те зависимости, которые мы подключали во время разработки. Сделать это можно следующим образом:</p>
6
<p>Установку зависимостей нужно выполнить еще до создания образа, так как во время первой установки формируется файл<em>package-lock.json</em>. Он нужен для фиксации зависимостей: с его помощью мы гарантируем, что в образе будут использоваться ровно те зависимости, которые мы подключали во время разработки. Сделать это можно следующим образом:</p>
7
<p>Теперь директория с приложением выглядит так:</p>
7
<p>Теперь директория с приложением выглядит так:</p>
8
<p>Docker создает образ на основе файла<em>Dockerfile</em>, в котором описываются необходимые команды. Мы начнем сразу с примера:</p>
8
<p>Docker создает образ на основе файла<em>Dockerfile</em>, в котором описываются необходимые команды. Мы начнем сразу с примера:</p>
9
<p>В основном, команды<em>Dockerfile</em>интуитивно понятны. Видно, что мы "упаковываем" приложение в образ, выполняем установку зависимостей и описываем то, как его запустить. Подробнее о командах мы поговорим позже, а сейчас посмотрим, как собирается, запускается и пушится образ в Docker Hub.</p>
9
<p>В основном, команды<em>Dockerfile</em>интуитивно понятны. Видно, что мы "упаковываем" приложение в образ, выполняем установку зависимостей и описываем то, как его запустить. Подробнее о командах мы поговорим позже, а сейчас посмотрим, как собирается, запускается и пушится образ в Docker Hub.</p>
10
<p>Для сборки образа в директории с<em>Dockerfile</em>нужно выполнить команду указанную ниже:</p>
10
<p>Для сборки образа в директории с<em>Dockerfile</em>нужно выполнить команду указанную ниже:</p>
11
<p>Сборка образа занимает какое-то время: нужно подождать, пока выполнятся все команды. Как результат, в списке образов появляется образ с именем<em>hexlet/docker-fastify-example</em>и тегом<em>latest</em>. Его можно запустить и убедиться в работоспособности:</p>
11
<p>Сборка образа занимает какое-то время: нужно подождать, пока выполнятся все команды. Как результат, в списке образов появляется образ с именем<em>hexlet/docker-fastify-example</em>и тегом<em>latest</em>. Его можно запустить и убедиться в работоспособности:</p>
12
<p>Для полной проверки, откройте в браузере ссылку<a>http://localhost:3000</a>и убедитесь что сайт открылся. Остался последний шаг - загрузить образ на Docker Hub. Для этого понадобится подготовительная работа:</p>
12
<p>Для полной проверки, откройте в браузере ссылку<a>http://localhost:3000</a>и убедитесь что сайт открылся. Остался последний шаг - загрузить образ на Docker Hub. Для этого понадобится подготовительная работа:</p>
13
<ol><li>Регистрация<a>https://hub.docker.com/</a></li>
13
<ol><li>Регистрация<a>https://hub.docker.com/</a></li>
14
<li>Подключение к аккаунту через запуск команды docker login в терминале. Docker попросит ввести имя пользователя и пароль</li>
14
<li>Подключение к аккаунту через запуск команды docker login в терминале. Docker попросит ввести имя пользователя и пароль</li>
15
<li>Создание репозитория с именем<em>docker-fastify-example</em>в<a>личном кабинете</a></li>
15
<li>Создание репозитория с именем<em>docker-fastify-example</em>в<a>личном кабинете</a></li>
16
</ol><p>Теперь, чтобы загрузить образ в Docker Hub, мы должны дать ему правильное имя. По соглашению, часть имени Docker-образа до символа<em>/</em>, должна совпадать с именем вашего пользователя Docker Hub. Чтобы так сделать, вам необходимо запустить команду сборки еще раз:</p>
16
</ol><p>Теперь, чтобы загрузить образ в Docker Hub, мы должны дать ему правильное имя. По соглашению, часть имени Docker-образа до символа<em>/</em>, должна совпадать с именем вашего пользователя Docker Hub. Чтобы так сделать, вам необходимо запустить команду сборки еще раз:</p>
17
<p>Теперь можно<em>пушить</em>:</p>
17
<p>Теперь можно<em>пушить</em>:</p>
18
<p>Если репозиторий публичный, то скачать и запустить этот образ сможет любой человек, с доступом в интернет.</p>
18
<p>Если репозиторий публичный, то скачать и запустить этот образ сможет любой человек, с доступом в интернет.</p>
19
<h2>Теги</h2>
19
<h2>Теги</h2>
20
<p>Теги у Docker-репозиториев изменяемые. Если изменить образ и снова его запушить с тем же тегом, образ поменяется. Для тега<em>latest</em>это ожидаемое поведение, а вот для версий нет. За этим нужно следить самостоятельно и не менять образ для уже существующих тегов. Если меняется образ, то правильно создавать новый тег:</p>
20
<p>Теги у Docker-репозиториев изменяемые. Если изменить образ и снова его запушить с тем же тегом, образ поменяется. Для тега<em>latest</em>это ожидаемое поведение, а вот для версий нет. За этим нужно следить самостоятельно и не менять образ для уже существующих тегов. Если меняется образ, то правильно создавать новый тег:</p>
21
<h2>Команды Dockerfile</h2>
21
<h2>Команды Dockerfile</h2>
22
<p><em>Dockerfile</em>состоит из команд, которые выполнятся сверху вниз по очереди, формируя файловую систему образа. Каждая последующая команда "видит" результаты предыдущей команды. Ниже мы разберем наиболее популярные команды, которые встречаются в большинстве образов.</p>
22
<p><em>Dockerfile</em>состоит из команд, которые выполнятся сверху вниз по очереди, формируя файловую систему образа. Каждая последующая команда "видит" результаты предыдущей команды. Ниже мы разберем наиболее популярные команды, которые встречаются в большинстве образов.</p>
23
<h3>FROM</h3>
23
<h3>FROM</h3>
24
<p>Образ - это в первую очередь файловая система, которая формируется на базе команд описанных в<em>Dockerfile</em>. Docker берет какую-то первоначальную файловую систему и затем изменяет ее в соответствии с описанием. Получившаяся структура файлов и становится образом. Откуда берется первоначальная файловая система?</p>
24
<p>Образ - это в первую очередь файловая система, которая формируется на базе команд описанных в<em>Dockerfile</em>. Docker берет какую-то первоначальную файловую систему и затем изменяет ее в соответствии с описанием. Получившаяся структура файлов и становится образом. Откуда берется первоначальная файловая система?</p>
25
<p>Практически все образы в Docker формируются не с нуля, а на базе уже существующих образов. Образы формируют дерево, в котором одни образы наследуют файловые системы других образов начиная с базового образа<a>scratch</a>.</p>
25
<p>Практически все образы в Docker формируются не с нуля, а на базе уже существующих образов. Образы формируют дерево, в котором одни образы наследуют файловые системы других образов начиная с базового образа<a>scratch</a>.</p>
26
<p>Команда FROM задает образ, чья файловая система берется за основу. Все последующие команды, которые изменяют файловую систему, работают уже с ней. Потому команда FROM идет первой в<em>Dockerfile</em>.</p>
26
<p>Команда FROM задает образ, чья файловая система берется за основу. Все последующие команды, которые изменяют файловую систему, работают уже с ней. Потому команда FROM идет первой в<em>Dockerfile</em>.</p>
27
<h3>WORKDIR</h3>
27
<h3>WORKDIR</h3>
28
<p>Команда WORKDIR задает рабочий каталог, относительно которого выполняются все действия во время формирования образа и при входе в контейнер:</p>
28
<p>Команда WORKDIR задает рабочий каталог, относительно которого выполняются все действия во время формирования образа и при входе в контейнер:</p>
29
<p>WORKDIR автоматически создает директорию, если ее еще нет.</p>
29
<p>WORKDIR автоматически создает директорию, если ее еще нет.</p>
30
<h3>COPY</h3>
30
<h3>COPY</h3>
31
<p>Команда COPY копирует файлы и директории с хост-машины внутрь Docker-образа. Она принимает два параметра: первый - что копируем, второй - куда копируем и под каким именем. Второй параметр может принимать три варианта:</p>
31
<p>Команда COPY копирует файлы и директории с хост-машины внутрь Docker-образа. Она принимает два параметра: первый - что копируем, второй - куда копируем и под каким именем. Второй параметр может принимать три варианта:</p>
32
<ul><li>Абсолютный путь, копирование происходит ровно по нему</li>
32
<ul><li>Абсолютный путь, копирование происходит ровно по нему</li>
33
<li>Относительный путь, копирование происходит относительно установленной рабочей директории WORKDIR</li>
33
<li>Относительный путь, копирование происходит относительно установленной рабочей директории WORKDIR</li>
34
<li>Точка, файл или директория копируется как есть в рабочую директорию</li>
34
<li>Точка, файл или директория копируется как есть в рабочую директорию</li>
35
</ul><p>Если точка идет первым параметром, то это обозначает что копироваться будет директория целиком.</p>
35
</ul><p>Если точка идет первым параметром, то это обозначает что копироваться будет директория целиком.</p>
36
<p>Для полного понимания принципов работы команды COPY, нужно представлять что такое контекст. Помните, когда мы указывали точку во время сборки образа? Это и есть контекст:</p>
36
<p>Для полного понимания принципов работы команды COPY, нужно представлять что такое контекст. Помните, когда мы указывали точку во время сборки образа? Это и есть контекст:</p>
37
<p>Контекст - это директория, относительно которой работает первый параметр в COPY. Обычно контекстом указывают ту директорию, которая содержит<em>Dockerfile</em>. Но это не обязательно, ведь контекстом может быть и другая директория:</p>
37
<p>Контекст - это директория, относительно которой работает первый параметр в COPY. Обычно контекстом указывают ту директорию, которая содержит<em>Dockerfile</em>. Но это не обязательно, ведь контекстом может быть и другая директория:</p>
38
<p>Во время сборки образа, контекст целиком копируется внутрь системных директорий Docker, из которых в образ переносится все, что указано в команде COPY. Из-за этого иногда возникают проблемы. Контекст может содержать директории, которые не должны попадать в образ, например, .git, или зависимости установленные локально (<em>node_modules</em>), так как они все равно устанавливаются заново во время сборки. Чтобы избежать их попадания во внутрь, нужно создать файл<em>.dockerignore</em>и указать там те директории и файлы, которые не должны быть частью контекста. Принцип работы файла такой же, как и у<em>.gitignore</em>.</p>
38
<p>Во время сборки образа, контекст целиком копируется внутрь системных директорий Docker, из которых в образ переносится все, что указано в команде COPY. Из-за этого иногда возникают проблемы. Контекст может содержать директории, которые не должны попадать в образ, например, .git, или зависимости установленные локально (<em>node_modules</em>), так как они все равно устанавливаются заново во время сборки. Чтобы избежать их попадания во внутрь, нужно создать файл<em>.dockerignore</em>и указать там те директории и файлы, которые не должны быть частью контекста. Принцип работы файла такой же, как и у<em>.gitignore</em>.</p>
39
<p>node_modules .git logs tmp</p>
39
<p>node_modules .git logs tmp</p>
40
<p>Игнорирование таких директорий и файлов дает дополнительный плюс. Чем меньше размер контекста, тем быстрее он копируется. Если не следить за его размером, то процесс копирования может увеличиться до десятков секунд и даже минут.</p>
40
<p>Игнорирование таких директорий и файлов дает дополнительный плюс. Чем меньше размер контекста, тем быстрее он копируется. Если не следить за его размером, то процесс копирования может увеличиться до десятков секунд и даже минут.</p>
41
<h3>RUN</h3>
41
<h3>RUN</h3>
42
<p>Команда RUN выполняет переданную строчку в терминале от пользователя<em>root</em>. С ее помощью вносятся основные изменения в файловую систему, добавляются пакеты, ставятся зависимости и так далее. Команд RUN может быть добавлено любое количество, обычно делают по одной команде на одно действие.</p>
42
<p>Команда RUN выполняет переданную строчку в терминале от пользователя<em>root</em>. С ее помощью вносятся основные изменения в файловую систему, добавляются пакеты, ставятся зависимости и так далее. Команд RUN может быть добавлено любое количество, обычно делают по одной команде на одно действие.</p>
43
<p>RUN выполняется в не интерактивном режиме, это значит, что если выполняемая команда запросит пользовательский ввод, например разрешение на установку чего-либо, то мы не сможем выбрать ответ<em>yes</em>. Поэтому все команды в RUN запускают в неинтерактивном режиме:</p>
43
<p>RUN выполняется в не интерактивном режиме, это значит, что если выполняемая команда запросит пользовательский ввод, например разрешение на установку чего-либо, то мы не сможем выбрать ответ<em>yes</em>. Поэтому все команды в RUN запускают в неинтерактивном режиме:</p>
44
<h3>CMD</h3>
44
<h3>CMD</h3>
45
<p>CMD задает команду, которая выполняется при запуске контейнера по умолчанию. Она используется только в том случае, если контейнер был запущен без указания команды</p>
45
<p>CMD задает команду, которая выполняется при запуске контейнера по умолчанию. Она используется только в том случае, если контейнер был запущен без указания команды</p>
46
<h3>ENV</h3>
46
<h3>ENV</h3>
47
<p>Задает переменные окружения. Команды, выполняющиеся после ENV, видят эти переменные и могут их использовать.</p>
47
<p>Задает переменные окружения. Команды, выполняющиеся после ENV, видят эти переменные и могут их использовать.</p>
48
<p>С этой командой нужно быть острожнее. Переменные окружения созданы для того, чтобы их можно было менять, а их указание в Dockerfile фиксирует значения. По этому случаю, в Dockerfile обычно указывают только те переменные окружения, которые не зависят от среды запуска, как в примере выше. Нам в любом случае надо указать, что сервер должен запускаться на 0.0.0.0, иначе его будет невозможно увидеть снаружи. В большинстве ситуаций переменные окружения передаются снаружи для конкретного запуска:</p>
48
<p>С этой командой нужно быть острожнее. Переменные окружения созданы для того, чтобы их можно было менять, а их указание в Dockerfile фиксирует значения. По этому случаю, в Dockerfile обычно указывают только те переменные окружения, которые не зависят от среды запуска, как в примере выше. Нам в любом случае надо указать, что сервер должен запускаться на 0.0.0.0, иначе его будет невозможно увидеть снаружи. В большинстве ситуаций переменные окружения передаются снаружи для конкретного запуска:</p>
49
49