Как в Docker реализуется и за счет чего достигается multi-stage сборка?

«Как в Docker реализуется и за счет чего достигается multi-stage сборка?» — вопрос из категории Docker, который задают на 23% собеседований Devops Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Multi-stage сборка в Docker достигается за счет использования в одном Dockerfile нескольких инструкций FROM, каждая из которых начинает новый этап (stage). Это позволяет отделить среду сборки (со всеми компиляторами и зависимостями) от финального рантайм-образа.

Принцип работы:

  1. Первый этап (часто называемый builder) основан на большом образе с инструментами разработки (например, golang:1.21, maven:3.8). Здесь выполняется компиляция, установка зависимостей и сборка артефактов.
  2. Последующие этапы основаны на минимальных образах (например, alpine:latest, scratch). С помощью инструкции COPY --from=<stage_name> в них копируются только необходимые артефакты (бинарники, библиотеки) из предыдущих этапов.
  3. Промежуточные слои и инструменты сборки остаются в истории, но не попадают в финальный образ, что радикально уменьшает его размер и повышает безопасность (меньше attack surface).

Пример для Go-приложения:

# --- STAGE 1: Builder ---
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./ 
RUN go mod download
COPY . .
# Статическая линковка для работы в пустом образе 'scratch'
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /myapp .

# --- STAGE 2: Runtime ---
FROM scratch
# Копируем только скомпилированный бинарник из этапа 'builder'
COPY --from=builder /myapp /myapp
# Копируем SSL сертификаты, если приложение делает HTTPS-запросы
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
CMD ["/myapp"]

Преимущества:

  • Размер: Финальный образ может весить мегабайты вместо гигабайт.
  • Безопасность: В продакшене нет компилятора, исходного кода или пакетов для разработки.
  • Чистота: Упрощается управление зависимостями, так как среда выполнения изолирована от среды сборки.