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задаёт аргументы по умолчанию для него. - Без
ENTRYPOINTCMD— полная команда запуска контейнера. - В 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 \
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 /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 от "рабочего, но хрупкого":
- Минимальный базовый образ —
alpine,slim,distrolessвместо полногоubuntuилиnode:latest. - Порядок слоёв под кэш — сначала манифесты зависимостей, потом
RUN install, потом исходники. - Один
RUNна логическую операцию — объединяйте команды через&&, очищайте кэш пакетного менеджера в той же строке. .dockerignore— исключайте.git,node_modules, тесты, секреты.COPYвместоADD— если не нужна распаковка tar или загрузка по URL.- Не root — создайте пользователя и переключитесь через
USER. - Exec-формат для
CMD/ENTRYPOINT—["node", "server.js"], а не shell-форма (иначе сигналы вроде SIGTERM не доходят до процесса). - Multi-stage — для компилируемых языков и фронтенда с отдельной сборкой.
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.txt→pip install→COPY . .— кэш зависимостей при правке только кода. 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 сервиса; - использование одного огромного образа "на все случаи".
Каждый из этих пунктов обычно проявляется в виде долгих сборок, нестабильных релизов или повышенных рисков безопасности.
Связанные статьи
- DevOps — шпаргалка — 9 практик Dockerfile
- Docker
- docker-compose
- Docker Compose — готовые стеки
- Dockerfile — 10 типовых образов
- Объекты Docker
- Работа с Docker