Один Dockerfile для разработки и выпуска: выбираем target
Для разработки нужны отладчик и исходники, а выпускаемому сервису достаточно программы и её зависимостей. Два независимых Dockerfile быстро расходятся: исправление версии Python попадает только в один, и окружения снова отличаются. Несколько именованных стадий помогают держать общую основу рядом с двумя вариантами запуска.
Стадия — отдельная последовательность сборки после FROM. Имя после AS позволяет выбрать её через --target. Если цель не указать, результатом становится последняя стадия Dockerfile. Поэтому порядок имеет значение, но для команды полезнее ещё и явно записать выбранную цель в конфигурации.
Отделим общую основу от назначения
Пример запускает стандартный HTTP-сервер Python, поэтому не требует внешних пакетов. Поместите index.html с любым текстом в каталог site рядом с Dockerfile. Обе стадии получают одинаковую основу, но dev хранит исходник в /src, а runtime — только публикуемую страницу в /app.
FROM python:3.12-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1
FROM base AS dev
WORKDIR /src
COPY site/ /src/
CMD ["python", "-m", "http.server", "8000", "--bind", "0.0.0.0"]
FROM base AS runtime
WORKDIR /app
COPY site/index.html /app/index.html
USER 10001:10001
CMD ["python", "-m", "http.server", "8000", "--bind", "0.0.0.0"]
Сборка docker build --target dev -t page:dev . создаёт среду разработки. Команда docker build --target runtime -t page:release . выбирает выпуск. Для проверки запустите выбранный образ с -p 127.0.0.1:8000:8000 и откройте страницу на локальном порту. Числовой пользователь runtime читает страницу, но не обязан владеть исходниками.
В сервисе Compose задайте build.context: . и build.target: dev либо runtime. Выбор стадии находится именно внутри build, а не рядом с command. При изменении target образ нужно пересобрать; простой перезапуск существующего контейнера не меняет содержимое образа.
Разделение должно отражать реальные зависимости
В прикладном проекте общая стадия устанавливает зависимости запуска, а dev добавляет инструменты тестирования. Если runtime наследуется от dev, инструменты разработки всё равно попадут в итоговый образ. Для независимых ветвей наследуйте обе стадии от общей основы или переносите готовый результат сборки через COPY --from.
BuildKit строит зависимости выбранной стадии, поэтому независимая соседняя стадия не является обязательным шагом выпуска. Проверку тестов, находящуюся только в отдельной стадии test, нужно явно запускать в CI. Наличие тестовой стадии в файле ещё не означает, что каждый release прошёл тесты.
Проверьте получателя образа
После сборки сравните рабочий каталог и пользователя двух образов через docker image inspect. Затем проверьте страницу в runtime без bind mount исходников: иначе локальный каталог может скрыть недостающий файл образа. В CI сохраняйте команду сборки с target вместе с тегом, чтобы было ясно, какой вариант отправлен в registry.
Версии базовых образов и зависимости приложения также нужно фиксировать по принятой политике проекта. Сам target описывает маршрут сборки, но не делает изменяемый тег неизменяемым. Такой Dockerfile удобен именно как явная карта: от общей основы до двух различных способов использования приложения.
В курсе Docker и Docker Compose вы напишете Dockerfile, разберёте многоэтапную сборку и кэш, затем упакуете приложение с PostgreSQL для передачи другому разработчику. Откройте программу и соберите окружение, в котором цель сборки понятна всей команде.
Продолжить на практике
- Docker и Docker Compose: контейнеризация приложений ↗
Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.
