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

> Приём в Dockerfile, при котором программа собирается в одном образе со всеми инструментами, а в итоговый образ копируются только готовые артефакты.

Источник: https://llmdocs.ru/wiki/multi-stage-build/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Появление**: Docker 17.05 (май 2017)
- **Где описывается**: Dockerfile
- **Ключевые инструкции**: FROM ... AS name, COPY --from=name
- **Сборщик**: BuildKit (по умолчанию с Docker 23.0)
- **Поддержка**: Docker, Podman/Buildah, kaniko
- **Цель**: маленькие и безопасные образы
**Многоэтапная сборка** (англ. *multi-stage build*) — способ описать в одном [Dockerfile](https://llmdocs.ru/wiki/dockerfile.md) несколько последовательных этапов, каждый со своим базовым образом. На первых этапах ставятся компиляторы, менеджеры пакетов и зависимости для сборки, а в финальный образ копируются только готовые файлы — бинарник, собранная статика или установленные библиотеки. В результате образ получается в разы меньше, быстрее скачивается и содержит меньше программ, в которых могут найтись уязвимости.

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

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

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

| Стек | Один этап | Многоэтапная |
|---|---|---|
| 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]

```dockerfile
# этап 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](https://llmdocs.ru/wiki/go.md) особенно хорошо подходит для этого: статически слинкованный бинарник может работать даже в пустом образе `scratch`. Образы **distroless** от Google содержат только библиотеки времени выполнения — без shell и менеджера пакетов.[5]

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

```dockerfile
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](https://llmdocs.ru/wiki/nodejs.md) нужен только для сборки, а раздаёт статику [nginx](https://llmdocs.ru/wiki/nginx.md).

## Python

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

```dockerfile
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](https://llmdocs.ru/wiki/ci-cd.md);
- поддерживает **кэш-монтирование**: `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](https://llmdocs.ru/wiki/podman.md) и Buildah, kaniko в [Kubernetes](https://llmdocs.ru/wiki/kubernetes.md).

## См. также

- [Dockerfile](https://llmdocs.ru/wiki/dockerfile.md)
- [Docker](https://llmdocs.ru/wiki/docker.md)
- [Образ контейнера и OCI](https://llmdocs.ru/wiki/oci-image.md)
- [Container registry](https://llmdocs.ru/wiki/container-registry.md)

## Источники

1. [Docker Docs — Multi-stage builds](https://docs.docker.com/build/building/multi-stage/)
2. [Dockerfile reference](https://docs.docker.com/reference/dockerfile/)
3. [Docker Docs — BuildKit](https://docs.docker.com/build/buildkit/)
4. [Docker Docs — Optimize cache usage in builds](https://docs.docker.com/build/cache/optimize/)
5. [GoogleContainerTools — distroless](https://github.com/GoogleContainerTools/distroless)
