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

Материал из llmdocs — справочник разработчика
Многоэтапная сборка (multi-stage)
ПоявлениеDocker 17.05 (май 2017)
Где описываетсяDockerfile
Ключевые инструкцииFROM ... AS name, COPY --from=name
СборщикBuildKit (по умолчанию с Docker 23.0)
ПоддержкаDocker, Podman/Buildah, kaniko
Цельмаленькие и безопасные образы

Многоэтапная сборка (англ. multi-stage build) — способ описать в одном Dockerfile несколько последовательных этапов, каждый со своим базовым образом. На первых этапах ставятся компиляторы, менеджеры пакетов и зависимости для сборки, а в финальный образ копируются только готовые файлы — бинарник, собранная статика или установленные библиотеки. В результате образ получается в разы меньше, быстрее скачивается и содержит меньше программ, в которых могут найтись уязвимости.

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

До появления многоэтапной сборки в 2017 году (Docker 17.05) разработчики либо тащили в продакшен образ с полным набором инструментов, либо держали два Dockerfile и скрипт, который копировал артефакты из одного контейнера в другой («builder pattern»).[1] Каждая инструкция Dockerfile создаёт слой, и удаление файла в следующем слое не уменьшает образ — файл остаётся в предыдущем. Многоэтапная сборка решает это: в финальный образ попадают только слои последнего этапа.

Типичный выигрыш:

Стек Один этап Многоэтапная
Go golang ~800 МБ scratch/distroless 10–20 МБ
Node.js (фронтенд) node ~1 ГБ nginx:alpine ~50 МБ
Python python ~1 ГБ python:slim 150–250 МБ
Java JDK + Maven ~700 МБ JRE ~200 МБ

Синтаксис

Этапу дают имя через AS, а файлы из него копируют COPY --from=имя:[2]

# этап 1: сборка
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/server

# этап 2: запуск
FROM gcr.io/distroless/static-debian12
COPY --from=build /app /app
USER nonroot
ENTRYPOINT ["/app"]

Go особенно хорошо подходит для этого: статически слинкованный бинарник может работать даже в пустом образе scratch. Образы distroless от Google содержат только библиотеки времени выполнения — без shell и менеджера пакетов.[5]

Фронтенд-пример:

FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html

Здесь Node.js нужен только для сборки, а раздаёт статику nginx.

Python

Для интерпретируемых языков переносят не бинарник, а установленное окружение:

FROM python:3.12 AS build
RUN pip install uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev

FROM python:3.12-slim
WORKDIR /app
COPY --from=build /app/.venv /app/.venv
COPY . .
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "app"]

Компиляторы для пакетов с C-расширениями остаются на этапе сборки. Важно, чтобы базовые образы этапов совпадали по версии Python и libc.

Возможности BuildKit

С Docker 23.0 сборку по умолчанию выполняет BuildKit, который:[3]

  • строит независимые этапы параллельно и пропускает этапы, не нужные для цели;
  • позволяет собрать конкретный этап: docker build --target test . — удобно для прогона тестов в CI/CD;
  • поддерживает кэш-монтирование: RUN --mount=type=cache,target=/root/.cache/pip pip install ... — кэш менеджера пакетов сохраняется между сборками, но не попадает в образ;
  • передаёт секреты без записи в слои: RUN --mount=type=secret,id=npmrc ...;
  • копирует файлы из любого образа: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv.

Практические советы

  • Копируйте сначала файлы зависимостей (go.mod, package.json, uv.lock), затем исходники — так слой с зависимостями кэшируется, пока они не меняются.[4]
  • Фиксируйте версии базовых образов (node:22.11, а лучше по дайджесту), чтобы сборка была воспроизводимой.
  • Запускайте финальный образ не от root (USER).
  • Добавьте .dockerignore — node_modules, .git и локальные .env не должны попадать в контекст сборки.
  • Отдельный этап test или lint можно запускать в CI, не включая инструменты тестирования в продакшен-образ.

Многоэтапные Dockerfile понимают и альтернативные сборщики: Podman и Buildah, kaniko в Kubernetes.

См. также

Источники

  1. Docker Docs — Multi-stage builds
  2. Dockerfile reference
  3. Docker Docs — BuildKit
  4. Docker Docs — Optimize cache usage in builds
  5. GoogleContainerTools — distroless