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

Dockerfile

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

Dockerfile

Грамотно написанный Dockerfile влияет на скорость сборки, стабильность релизов, безопасность и стоимость инфраструктуры. Поэтому его стоит рассматривать как полноценный инженерный артефакт проекта, а не как вспомогательный файл.


Что такое Dockerfile?

Dockerfile — текстовый файл с инструкциями для автоматической сборки Docker-образа. Он задаёт воспроизводимый процесс, по которому команда получает одинаковый результат на разных машинах.

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

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

Dockerfile — ключевая документация образа. Сборка по инструкциям файла:

docker build -t myapp .

Разбор:

  • docker build читает Dockerfile в контексте (здесь — текущая директория .).
  • -t myapp задаёт имя и тег образа для последующего docker run и push в registry.
  • Точка в конце — путь к контексту сборки; в него попадают только файлы, доступные для COPY/ADD.
  • Каждая инструкция Dockerfile создаёт слой; неизменённые слои берутся из кэша при повторной сборке.

Каждая инструкция создаёт слой (layer); слои кэшируются при повторных сборках.


Как работает сборка

При выполнении docker build Docker читает контекст сборки — обычно текущую директорию проекта (.). В контекст попадают все файлы, которые могут понадобиться инструкциям COPY и ADD. Исключить лишнее помогает файл .dockerignore (аналог .gitignore):

.git
node_modules
__pycache__
*.log
.env
dist
coverage

Разбор:

  • .git и node_modules не попадают в контекст — сборка быстрее, образ меньше.
  • __pycache__, dist, coverage — артефакты сборки и тестов, в runtime не нужны.
  • .env и *.log — типичный источник утечки секретов и мусора в слоях.
  • Файл действует как .gitignore: исключения применяются до отправки контекста демону Docker.

Слои и кэш. Каждая инструкция (FROM, RUN, COPY…) создаёт новый слой. При повторной сборке Docker проверяет, изменилась ли инструкция или файлы, которые она затрагивает. Если нет — берёт слой из кэша. Поэтому важен порядок инструкций: сначала копируют файлы, которые меняются редко (package.json, requirements.txt), устанавливают зависимости, и только потом — исходный код. Сводка форматов манифестов — Манифесты зависимостей.

Типичная ошибка: COPY . . в начале Dockerfile. Любое изменение в проекте инвалидирует кэш всех последующих RUN, и зависимости переустанавливаются заново.

Сборка с параметрами:

# Тег образа
docker build -t myapp:1.2.0 .

# Другой Dockerfile или контекст
docker build -f docker/Dockerfile.prod -t myapp:prod .

# Передача build-аргументов
docker build --build-arg NODE_ENV=production -t myapp .

Разбор:

  • -t myapp:1.2.0 — явный тег версии; без тега Docker подставит latest.
  • -f docker/Dockerfile.prod — другой файл и путь, если Dockerfile лежит не в корне контекста.
  • --build-arg передаёт значение в ARG внутри Dockerfile только на этапе сборки.
  • Контекст (последний аргумент .) и -f независимы: можно собирать из подкаталога с prod-файлом.

Play ITЗагрузка интерактивного демо…


Инструкции

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


FROM

FROM <image>:<tag>

Разбор:

  • Первая инструкция в Dockerfile; без FROM сборка не начнётся.
  • <image>:<tag> — образ из registry (Docker Hub, GHCR и т.д.) или локальный.
  • Каждый FROM в файле начинает новую стадию (важно для multi-stage).
  • Смена базового образа инвалидирует кэш всех последующих слоёв этой стадии.

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

Пример:

FROM ubuntu:20.04

Разбор:

  • Базовый слой — полноценный Ubuntu 20.04 (~70+ МБ минимум).
  • Тег 20.04 фиксирует LTS; latest для продакшена лучше не использовать.
  • Дальнейшие RUN/COPY накладываются поверх этого слоя.
  • Для production чаще берут ubuntu:22.04 или -slim/-alpine варианты баз.

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

FROM golang:1.20 AS builder

Разбор:

  • AS builder — имя стадии для COPY --from=builder в финальном образе.
  • Образ с Go SDK нужен только на этапе компиляции, не в runtime.
  • В multi-stage финальный FROM обычно минимальный (alpine, distroless).
  • Несколько FROM в одном файле — отдельные цепочки слоёв до финальной стадии.

LABEL

LABEL <key>=<value>

Разбор:

  • Пары ключ–значение хранятся в метаданных образа, не влияют на runtime напрямую.
  • Удобно для версии, владельца, лицензии, ссылок на документацию.
  • Несколько LABEL можно объединить в одну инструкцию или писать отдельными строками.
  • Оркестраторы и политики (OPA, Kyverno) иногда читают labels при деплое.

Добавляет метаданные к образу (автор, версия, описание). Просмотр меток:

docker inspect my-image

Разбор:

  • docker inspect выводит JSON с полями Config.Labels, Env, портами и слоями.
  • Имя my-image — тег или ID образа после docker build -t.
  • Полезно для отладки: какие метки реально попали в собранный образ.
  • В CI метки часто проставляют через LABEL или --label при docker build.

Пример:

LABEL maintainer="tim@mail.ru"

Разбор:

  • maintainer — устаревший, но узнаваемый ключ (часто заменяют на org.opencontainers.image.authors).
  • Значение в кавычках обязательно при пробелах и спецсимволах.
  • Метка попадает в каждый контейнер, созданный из этого образа.
  • Не путать с ENV — labels не видны приложению как переменные среды.

ENV

ENV <key>=<value>

Разбор:

  • Переменные доступны в RUN, CMD, ENTRYPOINT и в запущенном контейнере.
  • Значения "запекаются" в образ; при docker run -e можно переопределить.
  • Синтаксис ENV KEY=value или ENV KEY value (два аргумента).
  • Несколько переменных в одной строке: ENV A=1 B=2 — один слой вместо двух.

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

Пример:

ENV APP_HOME=/app

Разбор:

  • APP_HOME можно подставлять в WORKDIR $APP_HOME и пути COPY.
  • Путь /app — распространённая конвенция для приложений в контейнере.
  • Изменение ENV создаёт новый слой и сбрасывает кэш последующих инструкций.
  • Секреты в ENV остаются в истории образа — для них используют runtime-секреты.

RUN

RUN <command>

Разбор:

  • Команда выполняется во временном контейнере стадии сборки; результат фиксируется в новом слое.
  • Каждый отдельный RUN — отдельный слой; длинные цепочки лучше объединять через &&.
  • Shell-форма: RUN apt-get update (через /bin/sh -c).
  • Exec-форма — RUN ["apt-get", "install", "-y", "pkg"] — без оболочки, предсказуемее сигналы.

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

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

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

Пример:

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

Разбор:

  • apt-get update и install в одном RUN — один слой вместо двух.
  • -y подтверждает установку без интерактива (сборка не зависнет).
  • В production добавляют rm -rf /var/lib/apt/lists/* в той же строке — меньше размер слоя.
  • Смена этой строки пересобирает все последующие слои Dockerfile.

COPY

COPY <src> <dest>

Разбор:

  • Источник — путь относительно контекста сборки, не абсолютный путь хоста.
  • Назначение — путь внутри файловой системы образа (часто под WORKDIR).
  • Поддерживаются wildcards и COPY --from=stage при multi-stage.
  • Любое изменение файлов в <src> инвалидирует кэш этого слоя и всех ниже.

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

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

Пример:

COPY . /app

Разбор:

  • . — весь контекст (минус .dockerignore); удобно для быстрого прототипа.
  • Ранний COPY . ломает кэш: любая правка в проекте сбрасывает последующие RUN.
  • В production сначала копируют манифесты зависимостей, потом остальной код.
  • Права и владелец копируются как на хосте, если не задан --chown.

ADD

ADD <src> <dest>

Разбор:

  • Базовое копирование как у COPY плюс автораспаковка локальных .tar, .gz, .bz2.
  • Может скачивать по URL (редко нужно; лучше явный curl в RUN с проверкой checksum).
  • Распаковка архива создаёт отдельные файлы в <dest> без отдельного RUN tar.
  • Для обычных файлов проекта предпочитают COPY — поведение проще и предсказуемее.

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

Пример:

ADD archive.tar.gz /app

Разбор:

  • Архив распаковывается в /app (или в подкаталог, если в архиве есть корневая папка).
  • Имя файла должно попадать в контекст сборки или быть скачано по URL в ADD.
  • Один слой фиксирует и копирование, и распаковку.
  • Если распаковка не нужна — COPY archive.tar.gz + RUN tar -xf даёт больший контроль.

CMD

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

Разбор:

  • Exec-форма (JSON-массив) — процесс PID 1 получает сигналы напрямую (важно для graceful shutdown).
  • Если есть ENTRYPOINT, CMD задаёт аргументы по умолчанию для него.
  • Без ENTRYPOINT CMD — полная команда запуска контейнера.
  • В Dockerfile допустима только одна инструкция CMD (последняя побеждает).

Определяет команду запуска контейнера. Переопределение при docker run:

docker run my-image python3 other_script.py

Разбор:

  • Аргументы после имени образа заменяют CMD из Dockerfile (если нет ENTRYPOINT).
  • Здесь вместо app.py из образа запустится other_script.py.
  • Exec-форма CMD в образе тоже полностью подменяется этой командой shell-формата docker run.
  • Для отладки удобно; в проде лучше явный CMD/ENTRYPOINT в образе.

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

Пример

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

Разбор:

  • python3 — интерпретатор, app.py — скрипт в WORKDIR (путь относительный).
  • Exec-форма не вызывает shell — корректная передача SIGTERM при остановке.
  • При docker run image other.py эта команда целиком заменится аргументами run.
  • Один CMD на файл; дублирование оставляет только последнюю инструкцию.

ENTRYPOINT

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

Разбор:

  • Процесс из ENTRYPOINT всегда стартует; docker run дополняет аргументами, а не заменяет бинарник.
  • Exec-форма рекомендуется для сервисов и CLI-обёрток в контейнере.
  • Совместно с CMD задаёт "бинарник + аргументы по умолчанию".
  • Переопределить ENTRYPOINT можно только флагом --entrypoint у docker run.

Команда, которая всегда выполняется при старте. Аргументы из docker run дополняют ENTRYPOINT:

docker run my-image --debug

Разбор:

  • --debug дописывается к ENTRYPOINT как аргумент (итог: python3 app.py --debug).
  • Имя образа — всё, что идёт до первого аргумента, не похожего на флаг Docker.
  • Без ENTRYPOINT такой вызов заменил бы CMD, а не дополнил бы его.
  • Удобно для CLI-образов, где бинарник фиксирован, а флаги меняются при запуске.

Пример:

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

Разбор:

  • python3 и app.py выполняются при каждом docker run этого образа.
  • Аргументы из docker run добавляются в конец командной строки.
  • Shell-форма ENTRYPOINT python3 app.py оборачивает вызов в /bin/sh -c.
  • Одна ENTRYPOINT на Dockerfile; повтор перезаписывает предыдущую.

CMD и ENTRYPOINT решают разные задачи:

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

Типичный паттерн для CLI-утилит:

ENTRYPOINT ["python3", "app.py"]
CMD ["--port", "8080"]

Разбор:

  • Старт по умолчанию: python3 app.py --port 8080.
  • CMD здесь — только аргументы по умолчанию, не отдельная команда.
  • docker run myapp --debug заменит CMD, итог: python3 app.py --debug.
  • Паттерн для сервисов с фиксированным entrypoint и настраиваемыми флагами.

Запуск docker run myapp --debug выполнит python3 app.py --debug. В одном Dockerfile — одна ENTRYPOINT и одна CMD.

docker run myapp --debug

Разбор:

  • Подтверждает поведение пары ENTRYPOINT + CMD: --debug подставляется вместо --port 8080.
  • Имя образа myapp — тег после docker build -t myapp.
  • Для полной замены entrypoint нужен docker run --entrypoint ....
  • Тот же синтаксис используют в Kubernetes через args при заданном command.

WORKDIR

WORKDIR /path/to/workdir

Разбор:

  • Все последующие RUN, CMD, ENTRYPOINT, COPY с относительными путями идут от этой директории.
  • Несуществующий путь создаётся автоматически (как mkdir -p).
  • Несколько WORKDIR подряд — вложенная смена каталога (редко нужно).
  • Можно использовать переменные: WORKDIR $APP_HOME после ENV APP_HOME=/app.

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

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

Пример:

WORKDIR /app

Разбор:

  • /app — стандартный каталог приложения в контейнере.
  • COPY . . после этой строки кладёт файлы в /app, а не в корень /.
  • Относительные пути в CMD ["node", "server.js"] ищутся в /app.
  • Смена WORKDIR влияет только на инструкции ниже по файлу.

ARG

ARG <name>[=<default value>]

Разбор:

  • Действует только на этапе сборки; в запущенном контейнере по умолчанию недоступен (в отличие от ENV).
  • Значение передают: docker build --build-arg name=value.
  • Можно использовать в FROM, RUN, LABEL до первого ENV с тем же именем.
  • Объявление ARG до FROM действует только для инструкции FROM в той же стадии.

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

Пример:

ARG VERSION=1.0

Разбор:

  • VERSION=1.0 — значение по умолчанию, если --build-arg VERSION=... не передан.
  • Удобно для меток LABEL version=$VERSION или выбора артефакта сборки.
  • Смена build-arg инвалидирует кэш с места первого использования этой переменной.
  • Секреты через ARG попадают в историю слоёв — для токенов используют BuildKit secrets.

EXPOSE

EXPOSE <port>[/protocol]

Разбор:

  • Документирует намерение: какой порт слушает процесс внутри контейнера.
  • Протокол по умолчанию tcp; для UDP: EXPOSE 53/udp.
  • Не публикует порт на хост — для этого docker run -p 8080:8080.
  • Orchestrator и docker run -P могут опираться на список EXPOSE при автопубликации.

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

Пример:

EXPOSE 8080

Разбор:

  • Порт 8080 внутри контейнера (типичный для HTTP API на Java, Go, Spring).
  • Связка с docker run -p 8080:8080 пробрасывает трафик с хоста.
  • В Kubernetes порт указывают в containerPort в манифесте отдельно от EXPOSE.
  • Несколько портов: EXPOSE 8080 8443 или отдельные строки.

VOLUME

VOLUME ["/data"]

Разбор:

  • Объявляет каталог, данные в котором переживают пересоздание контейнера при mount.
  • JSON-форма с массивом путей — несколько томов в одной инструкции.
  • Анонимный volume создаётся, если при docker run не указан -v с именем.
  • Данные в VOLUME не попадают в docker commit так же, как обычные слои образа.

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

Пример:

VOLUME /var/lib/mysql

Разбор:

  • Каталог данных СУБД MySQL/MariaDB внутри контейнера.
  • При docker run -v mysql_data:/var/lib/mysql данные хранятся в named volume.
  • Без внешнего тома при удалении контейнера анонимный volume может остаться на хосте.
  • В production чаще монтируют том через compose/Kubernetes, а не только VOLUME в Dockerfile.

USER

USER <user>[:<group>]

Разбор:

  • Все последующие RUN, CMD, ENTRYPOINT выполняются от указанного UID/GID.
  • До первого USER команды идут от root (полные права в контейнере).
  • Пользователь должен существовать в образе — обычно создают через RUN adduser раньше.
  • Форма USER 1000:1000 — числовые id, если имени нет в /etc/passwd.

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

Пример:

USER appuser

Разбор:

  • Процесс приложения не root — снижение риска при компрометации контейнера.
  • RUN до USER (установка пакетов) часто оставляют от root, затем переключаются.
  • Файлы, созданные ранее от root, могут потребовать chown перед запуском от appuser.
  • В Kubernetes securityContext.runAsUser должен совпадать с UID в образе.

ONBUILD

ONBUILD <instruction>

Разбор:

  • Инструкция выполнится при сборке дочернего образа FROM ваш-образ.
  • В самом родительском образе при docker build триггер не срабатывает.
  • Типично для базовых SDK-образов: "при наследовании скопируй проект".
  • Цепочка ONBUILD может наследоваться — учитывайте при проектировании базовых image.

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

Пример:

ONBUILD COPY . /app

Разбор:

  • При FROM parent в дочернем Dockerfile автоматически выполнится COPY . /app.
  • Контекст копирования — сборка дочернего образа, не родителя.
  • Удобно для шаблонов; в современных проектах чаще явный COPY в своём Dockerfile.
  • ONBUILD нельзя наследовать повторно с того же триггера без снятия флага.

STOPSIGNAL

STOPSIGNAL signal

Разбор:

  • Сигнал отправляется процессу PID 1 при docker stop (до принудительного SIGKILL).
  • По умолчанию SIGTERM; для приложений с иной семантикой задают явно.
  • Имя без префикса SIG в Dockerfile: STOPSIGNAL SIGTERM или числовой код.
  • Корректный stop важен для graceful shutdown (drain соединений, flush БД).

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

Пример:

STOPSIGNAL SIGTERM

Разбор:

  • Стандартное корректное завершение Unix-процессов.
  • Приложение на Node/Go/Java должно обрабатывать SIGTERM в entrypoint (exec-форма CMD).
  • Таймаут остановки задаётся docker stop -t или stop_grace_period в compose.
  • Если процесс не реагирует, Docker по истечении таймаута шлёт SIGKILL.

SHELL

SHELL ["executable", "parameters"]

Разбор:

  • Меняет оболочку для shell-формы RUN, CMD, ENTRYPOINT (не для JSON exec).
  • На Linux по умолчанию /bin/sh -c; на Windows — cmd /S /C.
  • Exec-форма RUN ["..."] оболочку не использует.
  • Влияет на поведение set -e, pipefail и синтаксис в цепочках RUN.

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

Пример:

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

Разбор:

  • Дальнейшие RUN apt-get ... && ... выполняются через bash, а не dash.
  • Удобно для скриптов с bash-специфичным синтаксисом ([[ ]], массивы).
  • Увеличивает зависимость образа от наличия /bin/bash в базе.
  • Для минимальных образов (Alpine без bash) оставляют оболочку по умолчанию.

HEALTHCHECK

HEALTHCHECK [OPTIONS] CMD command

Разбор:

  • Docker периодически запускает команду проверки и выставляет статус healthy / unhealthy.
  • OPTIONS--interval, --timeout, --start-period, --retries.
  • CMD после опций — shell- или exec-форма проверки (HTTP, ping, скрипт).
  • HEALTHCHECK NONE отключает проверку, унаследованную от базового образа.

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

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

Разбор:

  • Каждые 30 с — запрос; ответ дольше 10 с считается сбоем проверки.
  • curl -f падает на HTTP 4xx/5xx; || exit 1 явно помечает unhealthy.
  • localhost — порт внутри того же контейнера, где крутится сервис.
  • В Alpine часто заменяют на wget -qO- — в slim-образах curl может отсутствовать.
  • Примеры health-check и флагов curlутилита curl, curl / fetch — примеры.

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


Многоэтапная сборка (multi-stage)

В одном Dockerfile можно указать несколько инструкций FROM. Каждая задаёт стадию сборки с собственным именем (AS builder). Финальный образ копирует только нужные артефакты из предыдущих стадий через COPY --from=.

Зачем это нужно:

  • в production-образ не попадают компиляторы, исходники и dev-зависимости;
  • размер образа уменьшается в разы;
  • сборка и runtime разделены логически.

Схема:

Схема: builder → runtime → компактный финальный образ (диаграмма).

Минимальный пример для Go:

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /server ./cmd/server

FROM alpine:3.19
RUN adduser -D appuser
USER appuser
COPY --from=builder /server /server
EXPOSE 8080
CMD ["/server"]

Разбор:

  • Стадия builder: SDK Go, go mod download до копирования исходников — кэш зависимостей.
  • CGO_ENABLED=0 — статический бинарник без glibc из builder-образа.
  • Финал на alpine:3.19: только бинарник, adduser + USER appuser — не root.
  • COPY --from=builder переносит артефакт без компилятора и исходников в runtime-образ.

В финальном образе — только статически собранный бинарник на Alpine (~15 МБ), без Go SDK (~800 МБ).


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

Таблица из девяти практик с минимальными примерами и связкой с командами docker build / push — в универсальной шпаргалке.

Перед разбором примеров — краткий чеклист того, что отличает production-ready Dockerfile от "рабочего, но хрупкого":

  1. Минимальный базовый образalpine, slim, distroless вместо полного ubuntu или node:latest.
  2. Порядок слоёв под кэш — сначала манифесты зависимостей, потом RUN install, потом исходники.
  3. Один RUN на логическую операцию — объединяйте команды через &&, очищайте кэш пакетного менеджера в той же строке.
  4. .dockerignore — исключайте .git, node_modules, тесты, секреты.
  5. COPY вместо ADD — если не нужна распаковка tar или загрузка по URL.
  6. Не root — создайте пользователя и переключитесь через USER.
  7. Exec-формат для CMD/ENTRYPOINT["node", "server.js"], а не shell-форма (иначе сигналы вроде SIGTERM не доходят до процесса).
  8. Multi-stage — для компилируемых языков и фронтенда с отдельной сборкой.
  9. HEALTHCHECK — для сервисов, которые оркестратор (Kubernetes, Swarm) должен мониторить.

Что дополнительно проверять перед релизом

Перед публикацией образа полезно пройти короткий quality-gate:

  • в Dockerfile нет секретов (TOKEN, PASSWORD, приватных ключей);
  • .dockerignore исключает временные файлы, тестовые артефакты и .env;
  • используются pinned-версии базовых образов;
  • в финальном образе нет компиляторов и build-toolchain;
  • приложение запускается от непривилегированного пользователя;
  • образ прошёл сканирование на CVE (docker scout, Trivy в CI);
  • есть команда для graceful shutdown и корректный healthcheck.

Это снижает риск инцидентов и повышает предсказуемость доставки.


Примеры готовых Dockerfile

Галерея для лабораторных и поиска

Десять готовых Dockerfile с построчным разбором, минимальным кодом приложения и типичными ошибками — Dockerfile — 10 типовых образов (формат как у галереи Turtle).

Ниже в этой главе — углублённые примеры для Node, Python, Go, SPA и Spring; для PHP, .NET и job-контейнера удобнее начать с галереи.

Ниже — пять типовых сценариев с полными файлами и пояснением ключевых решений.


1. Node.js API (Express / Fastify)

Структура проекта:

my-api/
├── package.json
├── package-lock.json
├── src/
│ └── server.js
├── Dockerfile
└── .dockerignore

Разбор:

  • package-lock.json рядом с package.json — основа для npm ci в Dockerfile.
  • src/ — исходники API; в образ копируют после установки node_modules.
  • Dockerfile и .dockerignore в корне — стандартный контекст docker build ..
  • Исключения в .dockerignore не дают node_modules с хоста перетереть слой в контейнере.

Dockerfile (production, multi-stage):

Код ITЗагрузка примера кода…

Разбор:

  • npm ci вместо npm install — детерминированная установка по lock-файлу; в CI всегда одинаковый результат.
  • --omit=dev — devDependencies (eslint, jest) не попадают в образ.
  • Две стадии: слой с node_modules пересобирается только при изменении package-lock.json.
  • wget в healthcheck — в Alpine нет curl по умолчанию.

Сборка и запуск:

docker build -t my-api:1.0 .
docker run -p 3000:3000 --name api my-api:1.0

Разбор:

  • docker build -t my-api:1.0 . — тег версии и контекст из корня my-api/.
  • -p 3000:3000 — проброс порта хост:контейнер (должен совпадать с EXPOSE).
  • --name api — удобное имя для логов и docker stop api.
  • Без -d контейнер в foreground; для фона добавляют -d.

2. Python (Flask / FastAPI)

Структура проекта:

flask-app/
├── app.py
├── requirements.txt
├── Dockerfile
└── .dockerignore

Разбор:

  • app.py — точка входа WSGI/ASGI, на неё ссылается gunicorn app:app.
  • requirements.txt копируют отдельно до COPY . . — кэш слоя pip install.
  • Один этап в примере; для меньшего образа gcc можно вынести в отдельную build-стадию.
  • .dockerignore исключает .venv и тесты с хоста.

Dockerfile:

Код ITЗагрузка примера кода…

Разбор:

  • python:3.12-slim — меньше полного образа, но совместимее с pip-пакетами, чем Alpine.
  • PYTHONDONTWRITEBYTECODE=1 — не создаёт .pyc в образе.
  • PYTHONUNBUFFERED=1 — логи сразу видны в docker logs.
  • gunicorn / uvicorn вместо dev-сервера Flask — корректная production-модель.
  • Блок с gcc нужен только для pip-пакетов с C-расширениями; для чисто Python-зависимостей его можно убрать.

requirements.txt (пример):

flask==3.0.0
gunicorn==21.2.0

Разбор:

  • Зафиксированные версии (==) — воспроизводимые сборки в CI.
  • gunicorn — production WSGI-сервер; Flask alone не для продакшена.
  • Файл копируют до исходников, чтобы слой pip install кэшировался.
  • Для FastAPI вместо gunicorn часто указывают uvicorn[standard].

3. Go-сервис (минимальный образ)

Структура проекта:

go-service/
├── go.mod
├── go.sum
├── cmd/
│ └── server/
│ └── main.go
└── Dockerfile

Разбор:

  • go.mod / go.sum в корне — модуль и checksums для go mod download.
  • cmd/server/main.go — типичная раскладка main в подкаталоге cmd/.
  • Сборка: go build ... ./cmd/server — путь пакета относительно WORKDIR /app.
  • Контекст — корень репозитория с исходниками и без лишних бинарников на хосте.

Dockerfile:

Код ITЗагрузка примера кода…

Разбор:

  • CGO_ENABLED=0 — статическая сборка без зависимости от glibc в runtime.
  • -ldflags="-s -w" — убирает отладочную информацию, уменьшает бинарник.
  • go mod download до COPY . . — кэш модулей не сбрасывается при правке исходников.
  • distroless/static + :nonroot — образ ~2 МБ поверх бинарника, процесс не root, нет /bin/sh.

4. React / Vue SPA + Nginx

Структура проекта:

frontend/
├── package.json
├── vite.config.js
├── src/
├── public/
├── Dockerfile
└── nginx.conf

Разбор:

  • vite.config.js и src/ — сборка фронта; артефакт обычно в dist/.
  • nginx.conf монтируется в образ отдельным COPY — маршруты SPA и proxy /api/.
  • package-lock.json обязателен для детерминированного npm ci в builder-стадии.
  • В финальный образ не попадают node_modules и исходники — только статика.

Dockerfile:

Код ITЗагрузка примера кода…

Разбор:

  • Builder на node:20-alpine: npm ci и npm run build — в runtime только dist.
  • Финал на nginx:1.25-alpine — статика из COPY --from=builder.
  • nginx.conf подменяет дефолтный vhost в conf.d.
  • HEALTHCHECK с wget — проверка отдачи index.html на порту 80.

nginx.conf (SPA с client-side routing):

server {
listen 80;
root /usr/share/nginx/html;
index index.html;

location / {
try_files $uri $uri/ /index.html;
}

location /api/ {
proxy_pass http://backend:3000/;
}
}

Разбор:

  • try_files $uri $uri/ /index.html — client-side routing без 404 на прямых URL.
  • location /api/ + proxy_pass — API на отдельном сервисе backend:3000 в сети compose/K8s.
  • root указывает на каталог, куда Dockerfile копирует dist.
  • listen 80 — HTTP внутри контейнера; TLS обычно на ingress или reverse proxy снаружи.

Полный разбор тех же приёмов вне Dockerfile — Nginx — конфиги под задачу.


5. Java Spring Boot (JAR)

Структура проекта:

spring-app/
├── pom.xml
├── src/
└── Dockerfile

Разбор:

  • pom.xml (или build.gradle) — описание зависимостей и цели package / bootJar.
  • src/main/java — исходники Spring; fat JAR собирается в target/*.jar.
  • Multi-stage Dockerfile копирует только JAR в runtime-стадию с JRE.
  • mvnw в репозитории позволяет собирать без установленного Maven на хосте.

Dockerfile:

Код ITЗагрузка примера кода…

Разбор:

  • JDK нужен только на стадии сборки; в runtime — JRE (меньше образ).
  • Spring Boot упаковывает приложение в fat JAR — достаточно java -jar.
  • Healthcheck через Spring Actuator — стандарт для Spring-приложений.
  • Для Gradle замените mvn package на ./gradlew bootJar.

Разбор на примере — Python по шагам

Разберём подробнее один Dockerfile — чтобы закрепить логику слоёв и кэша.

.dockerignore:

.git
__pycache__
*.log
*.pyc
.env
.venv
tests/

Разбор:

  • Исключают кэш Python и виртуальное окружение с хоста — меньше контекст и риск смешения версий.
  • .env и tests/ не должны попадать в образ и в слои COPY.
  • .git ускоряет отправку контекста демону Docker.
  • Согласован с примером пошагового Dockerfile ниже.

Dockerfile:

Код ITЗагрузка примера кода…

Разбор:

  • Порядок: requirements.txtpip installCOPY . . — кэш зависимостей при правке только кода.
  • PYTHONUNBUFFERED=1 — логи приложения сразу в docker logs.
  • USER appuser после adduser — процесс не root.
  • Dev-сервер через python app.py в учебном примере; в проде — gunicorn/uvicorn.
СтрокаЧто происходитКэш
FROM python:3.12-slimБазовый слой ~150 МБКэшируется, пока не меняется тег
COPY requirements.txtКопируется один файлСбрасывается при изменении зависимостей
RUN pip install …Устанавливаются пакетыСбрасывается вместе с requirements
COPY . .Копируется код приложенияСбрасывается при любом изменении кода
USER appuserКонтейнер не root
CMD ["python", "app.py"]Команда по умолчанию

Изменили только app.py — пересоберутся слои начиная с COPY . ., а pip install возьмётся из кэша.

Сборка и запуск:

docker build -t myapp .
docker run -p 5000:5000 myapp

Разбор:

  • docker build -t myapp . — тег для образа из пошагового Dockerfile выше.
  • -p 5000:5000 связывает EXPOSE 5000 с портом на хосте.
  • Повторный build после правки только app.py переиспользует кэш pip install (см. таблицу).
  • Для разработки с томом: -v $(pwd):/app — отдельный сценарий, не production.

Анти-паттерны Dockerfile

Даже рабочий Dockerfile может создавать проблемы в продакшене. Частые ошибки:

  • FROM <image>:latest без фиксации версии;
  • ранний COPY . ., который ломает кэш зависимостей;
  • установка dev-зависимостей в production-образ;
  • запуск процесса от root без причины;
  • отсутствие HEALTHCHECK у long-running сервиса;
  • использование одного огромного образа "на все случаи".

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


Связанные статьи