Как устроен Dockerfile

Содержание
Dockerfile - что это?
Dockerfile
— это текстовый файл, в котором описан рецепт создания образа Docker. Рецепт состоит из инструкций, которые выполняются последовательно. Они содержат информацию об операционной системе, выбранной платформе, фреймворках, библиотеках, инструментах, которые нужно установить.
Dockerfile позволяет создавать одинаковое окружение для работы программы, независимо от машины, которая собирает образ. Прочитайте обзорную статью о Docker, чтобы лучше разобраться, зачем это нужно.
Современные веб-приложения работают очень просто только для пользователя, который может смотреть видео, читать тексты, слушать музыку или заказывать еду «в один клик».
Под капотом — это сложный набор приложений, взаимодействующих между собой. Если одно из приложений в цепочке сломается, клиент не сможет решить свою задачу.
Важная задача веб-разработчика — сделать приложение максимально живучим в самых разных условиях. Решение задачи заключается в том, чтобы воспроизвести одинаковое, «правильное» поведение веб-приложения.
Docker умеет создать идентичные условия работы приложения, независимо от операционных систем и установленных служб на компьютере разработчика и на сервере. Для этого используется концепция контейнера. Прототипом контейнера является образ, в Dockerfile
описывают процесс его создания в виде набора инструкций.
Инструкции Dockerfile

Инструкции записываются построчно. На первом месте указывается команда для Docker, которую нужно выполнить, а затем — список аргументов этой команды:
# Comment INSTRUCTION arguments
Образ Docker легче всего представить в виде слоёного пирога или бургера: новый слой — новая инструкция. Образы для типичного веб-приложения могут быть собраны примерно так:

Каждая новая инструкция — новый слой. В качестве инструкции можно выполнить команду в терминале, скопировать файлы внутрь образа или настроить связь с внешним миром с помощью сетевого окружения и томов. Docker объединяет файловые системы отдельных слоёв в одну во время сборки, используя механизм Union File Systems.
Концепция слоёв позволяет Docker оптимальным образом хранить данные на жёстком диске. Docker загружает только те слои, которых не было на компьютере прежде. При этом слой из одного образа может подойти и к другому.
После того как слои образа описаны в файле конфигурации, необходимо произвести сборку образа с помощью команды:
docker build
Описание инструкций Dockerfile
Работу с Dockerfile
можно разбить на два этапа: описание инструкций и сборка. Ниже рассмотрим все возможные инструкции для для создания Docker образа.
FROM. Установка базового образа
Dockerfile обычно начинается с инструкции FROM
. Эта инструкция задаёт базовый образ.
В качестве базового образа может быть использован образ с чистой операционной системой, образ с уже установленной и настроенной платформой или вообще любой другой образ. Вот так можно установить AlmaLinux как базовый образ:
FROM almalinux:latest
Для веб-приложения на PHP обычно используют официальный образ от команды Docker:
FROM php
RUN. Запуск команд терминала
Инструкция RUN
позволяет запускать команды терминала при сборке. Это самая используемая инструкция, ей можно создать папку, установить недостающие пакеты или запустить shell скрипт.
Например, установим интерпретатор PHP поверх образа с чистой AlmaLinux:
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y RUN dnf install php -y
При сборке образа теперь будет произведено обновление системы и подключен репозиторий EPEL и установлен PHP последней версии.
COPY и ADD. Копирование файлов проекта
Инструкции COPY
и ADD
позволяют перенести файлы с компьютера, который запускает сборку, внутрь образа.
Создадим файл index.php c кодом:
<?php echo 'hello world';
Теперь перенесём все содержимое папки, где лежит Dockerfile
в папку /app
внутри образа:
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y COPY . /app
Используйте
COPY
, она только копирует указанную папку во внутреннюю папку образа. ИнструкцияADD
слишком всемогущая и можно случайно использовать её неверно. Например, она может скачать файл из Интернета перед копированием или разархивировать архив.
ENTRYPOINT и CMD. Запуск приложения
После того как образ готов, необходимо запустить приложение, которое в нем содержится. Образы Docker задумывались как упаковка для приложения, поэтому нет ничего удивительного в существовании механизма запуска приложения при старте контейнера на основе собранного образа. Для этого используют одну из двух инструкций: ENTRYPOINT
и CMD
.
Инструкция ENTRYPOINT
используется для запуска приложения при старте контейнера:
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y COPY . /app ENTRYPOINT ["php", "/app/index.php"]
В отличие от инструкции RUN
эта инструкция получает полный доступ к инфраструктуре терминала на компьютере пользователя. Вместе с командой запуска контейнера вы можете передавать параметры команде, которая прописана после ENTRYPOINT
или пользоваться системой сигналов Linux.
Затем необходимо собрать образ, указав явно имя образа для удобства:
docker build -t my-php-cli .
Запускать index.php можно теперь простой командой:
docker run --rm my-php-cli
Ключ --rm
означает, что после завершения работы контейнер удалится из списка использованных Docker. Это важно, поскольку, пока контейнер хранится в этом списке, нельзя запустить контейнер с таким же именем, несмотря на то, что контейнер уже отработал и не используется.
Инструкция CMD
делает практически то же самое. Обычно это также команда запуска приложения:
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y COPY . /app CMD ["php", "/app/index.php"]
CMD
— инструкция запуска по умолчанию, она игнорируется в том случае, если пользователь вашего образа прописывает в явном виде, что и как запускать после запуска контейнера на основе образа. Обычно CMD
вообще используется для передачи параметров по умолчанию вашему приложению, которые пользователь может переопределить.
В чем же разница между ENTRYPOINT
и CMD
? В ваших намерениях.
Используйте
ENTRYPOINT
, если вы не хотите, чтобы пользователь вашего образа переопределял поведение приложения в контейнере. ИспользуйтеCMD
, если записываете команду по умолчанию, которую пользователь с лёгкостью может переопределить на этапе запуска контейнера.
Есть две формы записи аргументов ENTRYPOINT
и CMD
: в виде строки и в виде массива строк. Первый вариант (так называемый shell режим) используется редко, поскольку не позволяет гибко настраивать работу образа. Обычно используется второй вариант (так называемый exec режим) — массив строк, который может состоять из команды и её параметров. Среди аргументов инструкции CMD
строка с командой может и отсутствовать, если эта инструкция идёт после инструкции ENTRYPOINT
. В этом случае строки массива рассматриваются как аргументы по умолчанию для команды, обозначенной в ENTRYPOINT
.
ENV. Переменные окружения
Переменные окружения задаются инструкцией ENV
.
Через переменные окружения передают ключи и пароли к сервисам, режим работы, другие секретные и не очень значения. Например, запуск приложения PHP для конечного пользователя обозначается дополнительной инструкцией:
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y RUN dnf install php -y COPY . /app ENV PHP_ENV=production CMD ["php", "/app/index.php"]
WORKDIR. Рабочая папка проекта
Инструкция WORKDIR
задаёт рабочую папку приложения. Все инструкции в Dockerfile
будут выполняться относительно неё.
Устанавливать рабочую папку — хороший тон. Она позволяет явно указать место, где будет происходить вся работа. Добавим её в нашу конфигурацию:
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y RUN dnf install php -y WORKDIR /app COPY . /app ENV PHP_ENV=production CMD ["php", "/app/index.php"]
USER. Запуск от имени пользователя
Если приложение нужно запускать от имени пользователя системы, то используйте инструкцию USER
с именем пользователя. Например, если вы хотите запускать приложение от имени пользователя php_user
, то конфигурационный файл будет выглядеть так:
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y RUN dnf install php -y WORKDIR /app COPY . /app ENV NODE_ENV=production USER node_user CMD ["php", "/app/index.php"]
EXPOSE. Проброска порта вовне
Для запуска веб-приложения на компьютере вы используете веб-сервер, запущенный локально. Обычно веб-приложение становится доступным по адресу http://localhost:8080
. Цифры в конце означают порт, открытый для запросов со стороны браузера или других приложений. Чтобы открыть в браузере веб-приложение, запущенное внутри контейнера, нужно «пробросить» запросы от браузера внутрь контейнера, а ответ от веб-приложения из контейнера наружу. Для этого используется перенаправление пакетов в виртуальном сетевом окружении (Docker Network):

На самом деле
EXPOSE
ничего не пробрасывает из контейнера в операционную систему. Используйте константу только для информационного характера.
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y RUN dnf install php -y WORKDIR /app COPY . /app ENV NODE_ENV=production USER node_user EXPOSE 8080 CMD ["php", "/app/index.php"]
Запись EXPOSE 8080
означает, что на компьютере, на котором запущен Docker, веб-приложение будет доступно по адресу http://localhost:8080
.
Для проброса портов используется команда:
docker run -p 8080:80 my-php-cli
ARG. Аргументы командной строки
Во время сборки образа не всегда удобно, а иногда даже опасно, описывать все параметры внутри Dockerfile
, поскольку этот файл обычно доступен в репозитории большинству разработчиков. В случае публичного репозитория это недопустимо вовсе. В этом случае следует пользоваться переменными, значения которых задаются на этапе сборки образа.
Передавать данные можно с помощью аргументов команды docker build
на этапе сборки образа. Во время сборки эти аргументы можно использовать как переменные, достаточно их определить инструкцией ARG
. Можно задать и значения по умолчанию на тот случай, если пользователь не укажет нужные аргументы. Например, передать имя пользователя внутрь контейнера можно следующим образом:
docker build --build-arg user=php_user .
В Dockerfile
надо будет добавить соответствующие инструкции:
FROM almalinux:latest RUN dnf update -y RUN dnf install epel-release -y RUN dnf install php -y WORKDIR /app COPY . /app ENV PHP_ENV=production # Значение по умолчанию 'deploy' (можно не указывать) ARG user=deploy USER $user EXPOSE 8080 CMD ["php", "/app/index.php"]
Важно, что так не следует передавать секретные данные, поскольку их можно будет увидеть в истории Docker:
docker history
Для безопасной передачи секретных данных лучше использовать тома Docker.
Сборка образа
Образ Docker можно собрать тремя способами:
– указав путь к папке PATH
;
– указав путь к репозиторию URL
;
– используя стандартный поток ввода –
.
Чаще всего используется первый способ с указанием пути. Самая простая команда для сборки образа:
docker build .
С помощью этой команды собираться образ будет из текущей папки (.
в конце), в которой должен быть Dockerfile
.
Использование нескольких Dockerfile
Иногда возникает необходимость использования нескольких вариантов сборок в одном проекте. В этом случае не обойтись без нескольких файлов с инструкциями. При сборке можно указать другое имя для файла конфигурации или относительный путь внутри PATH
, нужно использовать флаг -f
:
docker build -f containers/dockerfile-mode-1 .
Точно так же можно указать относительный путь для проекта или репозитория по некоторому URL
. Например, Docker может скачать не только репозиторий GitHub, но и произвольный архив с проектом, распаковать его и собрать образ:
docker build -f ctx/Dockerfile http://server/ctx.tar.gz
Поддерживаются архивы форматов bzip2, gzip, xz.
Файлы и папки проекта, исполняемый файл приложения, архив или репозиторий Git составляют контекст
образа. Но Docker позволяет собирать образы без контекста
из стандартного потока ввода. Собрать такой образ можно командой:
docker build - < Dockerfile
Исключение файлов из сборки .dockerignore
Если вам не нужно включать в образ какие-то папки или файлы из контекста
, добавьте в папку файл исключений .dockerignore
. В этом файле перечисляются в отдельных строках все пути или маски путей, которые не должны быть помещены в образ. Пример файла:
# Комментарий */temp* */*/temp* temp?
– */temp
позволяет не включать в образ файлы или папки, имена которых начинаются на temp
, и которые находятся в любой папке первого уровня (например, /somedir/temporary.txt
или /somedir/temp
);
– */*/temp*
— делает то же, но для папок второго уровня;
– temp?
— позволяет не включать в образ файлы и папки из корневой папки образа, имена которых начинаются на temp
и состоят из пяти символов, последний из которых может быть любым.
Рекомендации
Для того чтобы использовать образы эффективнее, необходимо следовать рекомендациям от команды Docker:
- Нужно создавать образы так, чтобы жизненным циклом контейнера можно было удобно управлять. Образ не должен хранить внутреннее состояние. Данные внутрь образа можно передать на этапе сборки с помощью аргументов командной строки, а на этапе работы контейнера можно пользоваться томами Docker.
- Необходимо понимать контекст запуска веб-приложения: папка проекта, удалённый ресурс (remote source) или репозиторий.
- Надо понимать, что
Dockerfile
может запускаться вне контекста через стандартный поток ввода. - Используйте файл
.dockerignore
для того, чтобы в образ попадали только нужные файлы и папки. От всего лишнего лучше избавиться на этапе сборки. - Используйте сборку приложения в несколько стадий. Это позволит существенно уменьшить размер образа.
- Не устанавливайте то, что не будете использовать в образе.
- Необходимо разделять приложения на обособленные части, которые способны выполняться независимо. Этот процесс носит название декаплинга (Decoupling).
- Минимизируйте количество слоёв в образе. Это повышает производительность образа как при сборке, так и при работе контейнера.
- Если параметры инструкции записываются в несколько строк (с помощью символа переноса строки
\
) необходимо выстраивать аргументы в алфавитном порядке. Это повышает читаемость файла и упрощает отладку. - Используйте кэш Docker только для тех слоёв, которые будут нужны для сборки других образов. Для этого достаточно добавить параметр
--no-cache=true
в команду сборкиdocker build
.