Перейти к основному содержимому

8.06. Dockerfile

Разработчику Архитектору Инженеру

Dockerfile

Что такое Dockerfile?

Dockerfile — это текстовый файл, содержащий инструкции для автоматической сборки Docker-образа. Он является ключевым элементом по работе с Docker, так как позволяет создавать образы, которые можно использовать для запуска контейнеров. Dockerfile позволяет описать процесс создания образа шаг за шагом, что делает его воспроизводимым и легко поддерживаемым. Один и тот же Dockerfile может быть использован для создания одинаковых образов на разных машинах. Каждая инструкция в Dockerfile создаёт новый слой (layer) образа. Это позволяет эффективно использовать кэширование и уменьшать размер конечного образа.

Dockerfile — это ключевая документация образа, которая описывает все зависимости, переменные среды и команды. Когда мы запускаем команду docker build, Docker читает Dockerfile и выполняет инструкции по порядку. Каждая инструкция создаёт новый слой (layer) в образе. Слои кэшируются, что ускоряет процесс сборки при повторных запусках.


Инструкции

Разберём инструкции Dockerfile.

FROM

FROM <image>:<tag>

Команда задаёт базовый образ, на основе которого будет создан новый образ. Это обязательная первая инструкция в Dockerfile.

Пример:

FROM ubuntu:20.04

FROM может использоваться с алиасом для многоэтапной сборки:

FROM golang:1.20 AS builder

LABEL

LABEL <key>=<value>

Добавляет метаданные к образу (например, автор, версия, описание). Метки могут быть просмотрены с помощью команды docker inspect.

Пример:

LABEL maintainer="tim@mail.ru"

ENV

ENV <key>=<value>

Устанавливает переменные среды, которые будут доступны внутри контейнера. Переменные сохраняются в образе и могут использоваться другими инструкциями.

Пример:

ENV APP_HOME=/app

RUN

RUN <command>

Выполняет команду в новом слое и создаёт новый образ.

Используется для установки пакетов, настройки системы. Есть два формата:

  • Shell-формат: RUN <command> (выполняется через /bin/sh -c).
  • Exec-формат: RUN ["executable", "param1", "param2"].

Пример:

RUN apt-get update && apt-get install -y python3

COPY

COPY <src> <dest>

Копирует файлы или папки из локальной файловой системы в контейнер.

  • <src> - путь к файлам/папкам на хосте.
  • <dest> - путь внутри контейнера.

Пример:

COPY . /app

ADD

ADD <src> <dest>

Аналогично COPY, но также может распаковывать локальные .tar файлы. Не рекомендуется использовать, если не требуется распаковка архивов.

Пример:

ADD archive.tar.gz /app

CMD

CMD ["executable", "param1", "param2"]

Определяет команду, которая будет выполнена при запуске контейнера. Может быть переопределена при запуске контейнера через docker run.

В файле может быть только одна инструкция CMD.

Пример

CMD ["python3", "app.py"]

ENTRYPOINT

ENTRYPOINT ["executable", "param1", "param2"]

Определяет команду, которая всегда будет выполняться при запуске контейнера. Аргументы, переданные через docker run, добавляются к команде ENTRYPOINT.

Пример:

ENTRYPOINT ["python3", "app.py"]

По сути, это альтернатива CMD, но более гибкая.

WORKDIR

WORKDIR /path/to/workdir

Задаёт рабочую директорию для последующих инструкций (RUN, CMD, ENTRYPOINT).

Если директория не существует, она будет создана.

Пример:

WORKDIR /app

ARG

ARG <name>[=<default value>]

Определяет переменную, которую можно передать во время сборки образа через флаг --build-arg.

Пример:

ARG VERSION=1.0

EXPOSE

EXPOSE <port>[/protocol]

Информирует Docker о том, что контейнер будет слушать указанный порт. Не открывает порт автоматически, используется для документации.

Пример:

EXPOSE 8080

VOLUME

VOLUME ["/data"]

Создаёт точку монтирования для работы с постоянным хранилищем. Позволяет сохранить данные вне контейнера.

Пример:

VOLUME /var/lib/mysql

USER

USER <user>[:<group>]

Задаёт пользователя и группу, от имени которых будут выполняться команды. По умолчанию команды выполняются от имени root.

Пример:

USER appuser

ONBUILD

ONBUILD <instruction>

Добавляет триггер для будущих сборок, основанных на текущем образе. Инструкция будет выполнена только при сборке дочернего образа.

Пример:

ONBUILD COPY . /app

STOPSIGNAL

STOPSIGNAL signal

Задаёт сигнал, который будет отправлен контейнеру при его остановке.

Пример:

STOPSIGNAL SIGTERM

SHELL

SHELL ["executable", "parameters"]

Переопределяет команду оболочку для выполнения инструкций. По умолчанию используется /bin/sh -c на Linux и cmd /S /C на Windows.

Пример:

SHELL ["/bin/bash", "-c"]

HEALTHCHECK

HEALTHCHECK [OPTIONS] CMD command

Определяет команду для проверки работоспособности контейнера. Параметры:

  • --interval: Интервал между проверками (по умолчанию 30 секунд).
  • --timeout: Таймаут для проверки (по умолчанию 30 секунд).
  • --retries: Количество попыток перед объявлением контейнера неработоспособным (по умолчанию 3). Пример:
HEALTHCHECK --interval=30s --timeout=10s \
CMD curl -f http://localhost/ || exit 1

В Dockerfile можно использовать и комментарии для документации, через #.


Лучшие практики и разбор

Как делать хороший Dockerfile?

Лучшие практики:

  • Используйте минимальные базовые образы (например, alpine), чтобы уменьшить размер конечного образа.
  • Объединяйте команды (&&), чтобы минимизировать количество слоёв:
RUN apt-get update && apt-get install -y package1 package2
  • Используйте .dockerignore, чтобы исключить ненужные файлы из сборки.
  • Используйте COPY вместо ADD, если не требуется распаковка архивов.
  • Обеспечьте безопасность, используя минимальные привилегии (USER).

Давайте создадим пример хорошего Dockerfile по этим практикам. Представим, что у нас есть простое Python-приложение, которое запускает веб-сервер на Flask. Структура проекта предполагает, что в корне лежат файлы:

  • app.py — основной файл приложения.
  • requirements.txt — список зависимостей Python.
  • .dockerignore — файл для исключения ненужных файлов.

Какое содержимое в .dockerignore?

# Исключаем ненужные файлы
.git
__pycache__
*.log
*.pyc
.env


Пример файла

Теперь создадим Dockerfile, который будет следовать всем этим рекомендованным практикам:

# 1. Используем минимальный базовый образ
FROM python:3.9-alpine

# 2. Устанавливаем метаданные
LABEL maintainer="tim@mail.ru"

# 3. Устанавливаем переменные среды
ENV APP_HOME=/app
ENV PYTHONUNBUFFERED=1 # Для немедленного вывода логов

# 4. Создаем рабочую директорию
WORKDIR $APP_HOME

# 5. Копируем зависимости и устанавливаем их
COPY requirements.txt .
RUN apk add --no-cache gcc musl-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del gcc musl-dev # Удаляем ненужные пакеты после установки

# 6. Копируем остальные файлы приложения
COPY . .

# 7. Создаем пользователя для работы приложения
RUN adduser -D appuser
USER appuser

# 8. Открываем порт
EXPOSE 5000

# 9. Запускаем приложение
CMD ["python", "app.py"]

Разбираем этот файл.

Минимальный базовый образ:

FROM python:3.9-alpine

Мы использовали alpine версию Python, которая значительно меньше по размеру, чем стандартный образ.

Метаданные:

LABEL maintainer="tim@mail.ru"

Мы добавили метку для документации, чтобы было понятно, кто поддерживает образ.

Переменные среды:

ENV APP_HOME=/app
ENV PYTHONUNBUFFERED=1

APP_HOME задает рабочую директорию.

PYTHONUNBUFFERED=1 гарантирует, что логи будут выводиться немедленно, без буферизации.

Рабочая директория:

WORKDIR $APP_HOME

Мы установили рабочую директорию, чтобы все последующие команды выполнялись в ней.

Установка зависимостей:

COPY requirements.txt .
RUN apk add --no-cache gcc musl-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del gcc musl-dev

Копируем только requirements.txt, чтобы кэширование работало эффективно. Устанавливаем временные зависимости (gcc, musl-dev) для сборки Python-пакетов, а затем удаляем их, чтобы уменьшить размер образа.

Используем флаг --no-cache-dir для pip, чтобы избежать сохранения кэша.

Копирование файлов:

COPY . .

Копируем остальные файлы приложения в рабочую директорию.

Безопасность:

RUN adduser -D appuser
USER appuser

Создаём нового пользователя appuser, и используем его для запуска приложения. Это повышает безопасность, так как приложение не работает от имени root.

Открытие порта:

EXPOSE 5000

Информируем Docker о том, что приложение будет слушать порт 5000.

Команда для запуска:

CMD ["python", "app.py"]

Определяем команду для запуска приложения.

После того, как мы собрали контейнер, нам остаётся перейти в нужную директорию нашего проекта, выполнить docker build и docker run.