0

Dockerfile

09.08.2023

Docker может создавать образы автоматически, читая инструкции из файла Dockerfile, по сути это текстовый документ, содержащий все команды, которые пользователь может вызвать в командной строке для сборки образа Images. С помощью команды docker build можно создать автоматизированную сборку, которая последовательно выполняет несколько инструкций командной строки.

Создавать новый образ есть смысл когда вы не можете найти подходящий для вашей задачи образ на hub.docker.com. Созданные образы можно также загружать на hub.docker.com и делать их доступными для других разработчиков.

Инструкции, при сборке образа обрабатываются сверху вниз. Слои в итоговом образе создают только инструкции FROMRUNCOPYADD. Другие инструкции занимаются настройкой, описывают метаданные, или сообщают Docker о том, что во время выполнения контейнера нужно что-то сделать, например — открыть какой-то порт или выполнить какую-то команду.

Этапы создания

  1. Необходимо создать докер файл это специальный файл с инструкциями для Docker по созданию нового образа, один докер файл для одного образа
  2. Дальше докер файл помещают в корне папки приложения то есть если у вас уже есть папка в которой находится приложение тогда помещаете докер файл в корень проекта, это удобно тем что если вы например используете Git для контроля версии, Dockerfile всегда будет в вашем репозитории вместе с приложением
  3. Каждая инструкция FROMRUNCOPYADD в Dockerfile будет создавать новый слой в новом образе
  4. При создании образа нужно указывать имя и тег для образа
  5. На основании готового образа, создается контейнер

Типичный вид файла

Теперь давайте посмотрим как выглядит докер файл:

Dockerfile 2

Это пример небольшого Dockerfile, в нём есть четыре инструкции:

  • Инструкция FROM указывает на то, какой базовый образ будет использоваться для вашего образа, в данном примере базовый образ называется python а версия alpine
  • Инструкция WORKDIR сдесь создаётся рабочая директория внутри образа. В данном примере указан путь /app и благодаря такой инструкции внутри образа будет создана папка. Рекомендуется всегда создавать папку внутри образа для вашего приложения, иначе можно например случайно перезаписать системные папки
  • Инструкция COPY мы копируем все файлы из локальной текущей папки (первая точка) в папку указанную в директории WORKDIR (вторая точка). Когда необходимо скопировать файл с текущей папки на компьютере в папку внутри образа можно указывать в качестве целевой папке просто точку
  • Инструкция CMD указывает, какая команда будет выполнена когда создается новый контейнер на основании уже созданного образа. В данном примере будет запущен процесс python и ему передаётся аргумент main.py. Иными словами мы запустим процесс python и он выполнит файл main.py, этот файл должен находиться в рабочей директории. Подразумевается что файл main.py мы скопируем с нашего компьютера на этапе инструкция COPY

Синтаксис файла Dockerfile

Работу с Dockerfile можно разбить на два этапа: описание инструкций и сборка образа. Набор инструкций — последовательность действий, чтобы собрать образ.

  1. FROM установка родительского базового образа
  2. RUN запуск команд терминала
  3. COPY копирование файлов проекта
  4. ADD копирует файлы по ссылке и распаковывавает локальные tar архивы
  5. ENTRYPOINT предоставляет команду с аргументами для вызова во время выполнения контейнера, причем аргументы не переопределяются в командной строке пользователем
  6. CMD предоставляет команду с аргументами для вызова во время выполнения контейнера, причем аргументы переопределяются в командной строке пользователем
  7. ENV устанавливает постоянные переменные среды
  8. WORKDIR задаёт рабочую папку проекта
  9. USER задаёзапуск приложения от имени пользователя
  10. EXPOSE указывает на необходимость открыть порт
  11. ARG задаёт переменные для передачи Docker во время сборки образа
  12. VOLUME создаёт точку монтирования для работы с постоянным хранилищем

FROM установка базового образа

Dockerfile обычно начинается с инструкции FROM. Эта инструкция задаёт базовый образ. В качестве базового образа может быть использован образ с чистой операционной системой, образ с уже установленной и настроенной платформой или вообще любой другой образ. Вот так можно установить Ubuntu 18.04 как базовый образ:

FROM ubuntu:18.04

Для установки Node.js alpine версии используют команду ниже:

FROM node

RUN запуск команд терминала

Инструкция RUN позволяет запускать команды терминала при сборке. Это самая используемая инструкция, ей можно создать папку, установить недостающие пакеты или запустить shell скрипт. Например, установим платформу Node.js поверх образа с чистой Ubuntu:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm

COPY копирование файлов проекта

Инструкции COPY позволяют перенести файлы с компьютера, который запускает сборку, внутрь образа. Например, перенесём все содержимое папки, где лежит Dockerfile в папку /app внутри образа:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
COPY . /app

ADD копирование файлов проекта

Инструкции ADD умеет скачивать файлы с сервера по ссылке и распаковывать tar-архивы.

Если собрать образ из этого Dockerfile и запустить контейнер, то окажется что команда ADD выкачала этот архив:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
ADD https://github.com/bessarabov/Moment/archive/1.3.1.tar.gz /app

В той же папке где лежит Dockerfile находится файл с архивом. Если собрать образ и запустить контейнер из этого образа то будет видно что ADD разархивировал этот tar.gz файл:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
ADD 1.3.1.tar.gz /app

Используйте COPY, она только копирует указанную папку во внутреннюю папку образа. Инструкция ADD слишком всемогущая и можно случайно использовать её неверно. Например, она может скачать файл из Интернета перед копированием или разархивировать архив.

ENTRYPOINT запуск приложения

После того как образ готов, необходимо запустить приложение, которое в нем содержится. Образы Docker задумывались как упаковка для приложения, поэтому нет ничего удивительного в существовании механизма запуска приложения при старте контейнера на основе собранного образа. Для этого используют инструкцию ENTRYPOINT. Инструкция используется для запуска приложения при старте контейнера. Вместе с командой запуска контейнера вы можете передавать параметры команде, которая прописана после ENTRYPOINT. Внутри контейнера можно запустить программу node и выполнить файл переданный через параметры /app/app.js:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
COPY . /app
ENTRYPOINT ["node", "/app/app.js"]

Есть две формы записи аргументов ENTRYPOINT: в виде строки и в виде массива строк. Первый вариант (так называемый shell режим) используется редко, поскольку не позволяет гибко настраивать работу образа. Обычно используется второй вариант (так называемый exec режим) — массив строк, который может состоять из команды и её параметров.

Используйте ENTRYPOINT, если вы не хотите, чтобы пользователь вашего образа переопределял поведение приложения в контейнере. Используйте CMD, если записываете команду по умолчанию, которую пользователь с лёгкостью может переопределить на этапе запуска контейнера.

CMD запуск приложения

Инструкция CMD делает практически то же самое, что и ENTRYPOINT. Обычно это также команда запуска приложения, она игнорируется в том случае, если пользователь вашего образа прописывает в явном виде, что и как запускать после запуска контейнера на основе образа. Часто CMD вообще используется для передачи параметров по умолчанию вашему приложению, которые пользователь может переопределить:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
COPY . /app
CMD ["node", "/app/app.js"]

Есть две формы записи аргументов CMD: в виде строки и в виде массива строк. Первый вариант (так называемый shell режим) используется редко, поскольку не позволяет гибко настраивать работу образа. Обычно используется второй вариант (так называемый exec режим) — массив строк, который может состоять из команды и её параметров. Среди аргументов инструкции CMD строка с командой может отсутствовать, если эта инструкция идёт после инструкции ENTRYPOINT. В этом случае строки массива рассматриваются как аргументы по умолчанию для команды, обозначенной в ENTRYPOINT.

Используйте ENTRYPOINT, если вы не хотите, чтобы пользователь вашего образа переопределял поведение приложения в контейнере. Используйте CMD, если записываете команду по умолчанию, которую пользователь с лёгкостью может переопределить на этапе запуска контейнера.

ENV переменные окружения

Переменные окружения задаются инструкцией ENV. Через переменные окружения передают ключи и пароли к сервисам, режим работы, другие секретные и не очень значения. Например, запуск приложения Node.js для конечного пользователя обозначается дополнительной инструкцией:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
COPY . /app
ENV NODE_ENV=production
CMD ["node", "/app/app.js"]

WORKDIR рабочая папка проекта

Инструкция WORKDIR задаёт рабочую папку приложения. Все инструкции в Dockerfile будут выполняться относительно неё. Устанавливать рабочую папку — хороший тон. Она позволяет явно указать место, где будет происходить вся работа. Добавим её в нашу конфигурацию:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
WORKDIR /app
COPY . .
ENV NODE_ENV=production
CMD ["node", "app.js"]

USER запуск от имени пользователя

Если приложение нужно запускать от имени пользователя системы, то используйте инструкцию USER с именем пользователя. Например, если вы хотите запускать приложение от имени пользователя node_user, то конфигурационный файл будет выглядеть так:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
WORKDIR /app
COPY . .
ENV NODE_ENV=production
USER node_user
CMD ["node", "app.js"]

EXPOSE проброска порта вовне

Для запуска веб-приложения на компьютере вы используете веб-сервер, запущенный локально. Обычно веб-приложение становится доступным по адресу http://localhost:8080. Цифры в конце означают порт, открытый для запросов со стороны браузера или других приложений. Чтобы открыть в браузере веб-приложение, запущенное внутри контейнера, нужно «пробросить» запросы от браузера внутрь контейнера, а ответ от веб-приложения из контейнера наружу. Для этого используется перенаправление пакетов в виртуальном сетевом окружении.

EXPOSE незаменим, когда в образе находится база данных и нам нужен доступ к ней вне контейнера. Запись EXPOSE 8080 означает, что на компьютере, на котором запущен Docker, веб-приложение будет доступно по адресу http://localhost:8080:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
WORKDIR /app
COPY . .
ENV NODE_ENV=production
USER node_user
EXPOSE 8080
CMD ["node", "app.js"]

ARG Аргументы командной строки

Во время сборки образа не всегда удобно, а иногда даже опасно, описывать все параметры внутри Dockerfile, поскольку этот файл обычно доступен в репозитории большинству разработчиков. В случае публичного репозитория это недопустимо вовсе. В этом случае следует пользоваться переменными, значения которых задаются на этапе сборки образа.

Передавать данные можно с помощью аргументов команды docker build на этапе сборки образа. Во время сборки эти аргументы можно использовать как переменные, достаточно их определить инструкцией ARG. Можно задать и значения по умолчанию на тот случай, если пользователь не укажет нужные аргументы. Например, передать имя пользователя внутрь контейнера можно следующим образом:

docker build --build-arg user=node_user .

В Dockerfile надо будет добавить соответствующие инструкции:

FROM ubuntu:18.04
RUN sudo apt update && sudo apt install nodejs && sudo apt install npm
WORKDIR /app
COPY . .
ENV NODE_ENV=production
# Значение по умолчанию 'deploy' (можно не указывать)
ARG user=deploy
USER $user
EXPOSE 8080
CMD ["node", "app.js"]

Важно, что так не следует передавать секретные данные, поскольку их можно будет увидеть в истории Docker: docker history. Для безопасной передачи секретных данных лучше использовать тома Docker.

VOLUME точка монтирования для работы с постоянным хранилищем

Инструкция VOLUME добавляет тома в образ, том это папка в одном или более контейнерах, проброшенная через Union File System (UFS). Тома могут быть расшарены или повторно использованы между контейнерами. Команда VOLUME используется для организации доступа вашего контейнера к директории на хосте (тоже самое, что и монтирование директории), команда определяет где контейнер будет хранить постоянные данные и получать к ним доступ:

VOLUME ["/opt/project"]

В примере выше создается точка монтирования /opt/project для любого контейнера, созданного из образа.

Многоступенчатая сборка образа

С точки зрения оптимизации сборки, уменьшения размера образа и ускорения приложения, образ можно собирать в несколько этапов. Например, с помощью платформы Node.js произвести сборку веб-приложения на первом этапе, а на втором — запустить готовый бандл с помощью веб-сервера. Операция копирования из первого промежуточного образа во второй целевой пройдёт совершенно незаметно. После сборки образ будет занимать мало дискового пространства, в нем будет все самое необходимое для работы веб-приложения:

# Сборка проекта на платформе Node.js
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Запуск приложения на сервере
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Имя промежуточного образа build-stage служит для передачи результата работы первой стадии сборки.

Рекомендации по сборке

Для того чтобы использовать образы эффективнее, необходимо следовать рекомендациям от команды Docker:

  1. Нужно создавать образы так, чтобы жизненным циклом контейнера можно было удобно управлять. Образ не должен хранить внутреннее состояние. Данные внутрь образа можно передать на этапе сборки с помощью аргументов командной строки, а на этапе работы контейнера можно пользоваться томами Docker
  2. Необходимо понимать контекст запуска веб-приложения: папка проекта, удалённый ресурс (remote source) или репозиторий
  3. Надо понимать, что Dockerfile может запускаться вне контекста через стандартный поток ввода
  4. Используйте файл .dockerignore для того, чтобы в образ попадали только нужные файлы и папки. От всего лишнего лучше избавиться на этапе сборки
  5. Используйте сборку приложения в несколько стадий. Это позволит существенно уменьшить размер образа
  6. Не устанавливайте то, что не будете использовать в образе
  7. Необходимо разделять приложения на обособленные части, которые способны выполняться независимо. Этот процесс носит название декаплинга (Decoupling)
  8. Минимизируйте количество слоёв в образе. Это повышает производительность образа как при сборке, так и при работе контейнера
  9. Если параметры инструкции записываются в несколько строк (с помощью символа переноса строки \) необходимо выстраивать аргументы в алфавитном порядке. Это повышает читаемость файла и упрощает отладку
  10. Используйте кэш Docker только для тех слоёв, которые будут нужны для сборки других образов. Для этого достаточно добавить параметр --no-cache=true в команду сборки docker build

Сборка образа

Образ Docker можно собрать тремя способами, чаще всего используется первый способ с указанием пути:

  1. Указав путь к папке PATH
  2. Указав путь к репозиторию URL
  3. Используя стандартный поток ввода 

Создаем новый образ, если вы уже находитесь в папке где есть докер файл вам достаточно указать в инструкции точку, docker попытается найти докер файл в текущую директорию если такой файл был найден в текущей директории, запустится процесс создания нового образа и сверху вниз шаг за шагом выполнятся все инструкции которые указаны в докер файл и в конце если всё прошло успешно будет создан новый образ. В данном примере будет создан новый образ но ID у этого образа будет случайный:

docker build .
Dockerfile 3

Если вы хотите добавить определённое имя созданному образу то нужно использовать опцию -t. Если тег не указан, Docker сам подставит тег latest:

docker build . -t my_calendar:4.1.3
Dockerfile 4
Dockerfile 5

После создания образа, можно создавать контейнер используя указанное ранее имя образа my_calendar.

Использование нескольких Dockerfile

Иногда возникает необходимость использования нескольких вариантов сборок в одном проекте. В этом случае не обойтись без нескольких файлов с инструкциями. При сборке можно указать другое имя для файла конфигурации или относительный путь внутри PATH, нужно использовать флаг -f:

docker build -f containers/dockerfile-mode-1 .

Точно так же можно указать относительный путь для проекта или репозитория по некоторому URL. Например, Docker может скачать не только репозиторий GitHub, но и произвольный архив с проектом, распаковать его и собрать образ (поддерживаются архивы форматов bzip2, gzip, xz):

docker build -f ctx/Dockerfile http://server/ctx.tar.gz

Файлы и папки проекта, исполняемый файл приложения, архив или репозиторий Git составляют контекст образа. Но Docker позволяет собирать образы без контекста из стандартного потока ввода. Собрать такой образ можно командой:

docker build - < Dockerfile

Исключение файлов из сборки .dockerignore

Если вам не нужно включать в образ какие-то папки или файлы из контекста, добавьте в папку файл исключений .dockerignore. В этом файле перечисляются в отдельных строках все пути или маски путей, которые не должны быть помещены в образ. Пример файла:

# Комментарий
*/temp*
*/*/temp*
temp?
  • */temp позволяет не включать в образ файлы или папки, имена которых начинаются на temp, и которые находятся в любой папке первого уровня например, /somedir/temporary.txt или /somedir/temp
  • */*/temp* делает то же, но для папок второго уровня
  • temp? позволяет не включать в образ файлы и папки из корневой папки образа, имена которых начинаются на temp и состоят из пяти символов, последний из которых может быть любым

Свежие комментарии

Подписка

Лучшие статьи


Fatal error: Uncaught Error: Call to a member function have_posts() on null in /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/blog.php:380 Stack trace: #0 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/widgets/latest-posts/widget.php(257): fox56_blog_grid(NULL, Array) #1 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/widgets/latest-posts/register.php(33): include('/home/host18670...') #2 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/class-wp-widget.php(394): Wi_Widget_Latest_Posts->widget(Array, Array) #3 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/widgets.php(837): WP_Widget->display_callback(Array, Array) #4 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(417): dynamic_sidebar('sidebar') #5 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(136): fox56_single_sidebar() #6 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/single.php(7): fox56_single_inner() #7 /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/single.php(23): fox56_single() #8 /home/host1867038/the-devops.ru/htdocs/www/wp-includes/template-loader.php(106): include('/home/host18670...') #9 /home/host1867038/the-devops.ru/htdocs/www/wp-blog-header.php(19): require_once('/home/host18670...') #10 /home/host1867038/the-devops.ru/htdocs/www/index.php(17): require('/home/host18670...') #11 {main} thrown in /home/host1867038/the-devops.ru/htdocs/www/wp-content/themes/fox/inc/blog.php on line 380