Что такое мультистейджинг (multi-stage builds) в Docker?

Ответ

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

Проблема, которую решает мультистейджинг: Без него итоговый образ включает в себя:

  • Компиляторы (gcc, JDK)
  • Менеджеры пакетов (npm, pip) со всеми dev-зависимостями
  • Исходный код
  • Артефакты промежуточных шагов Это увеличивает размер образа, поверхность для атак и время деплоя.

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

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

# --- STAGE 2: Runtime ---
# Используем минимальный runtime-образ
FROM scratch AS runtime
# Копируем ТОЛЬКО скомпилированный бинарник из стейджа 'builder'
COPY --from=builder /myapp /myapp
# Копируем SSL-сертификаты (необходимы для TLS-соединений)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Пользователь без привилегий для безопасности
USER 1000:1000
ENTRYPOINT ["/myapp"]

Преимущества с точки зрения DevOps:

  1. Безопасность:
    • Минимальная поверхность для атак (нет shell, пакетного менеджера).
    • Запуск от непривилегированного пользователя (USER 1000).
    • Использование scratch или distroless образов.
  2. Эффективность:
    • Размер финального образа может сократиться в 10-20 раз (с ~1GB до ~50MB).
    • Ускорение деплоя и pull/push операций в registry.
    • Лучшее использование кэша слоев Docker.
  3. Совместимость: Статически скомпилированные бинарные файлы (как в примере с Go) гарантированно работают в любом окружении, совместимом с целевой ОС.

Практика для CI/CD: В пайплайне сборки можно использовать отдельные стейджи для:

  • Тестирования: Стейдж builder используется для запуска unit-тестов и линтеров.
  • Сканирования уязвимостей: Анализируется стейдж builder на наличие уязвимостей в dev-зависимостях.
  • Production: В registry и на кластер подается только финальный стейдж runtime.

Итог: Мультистейджинг — это обязательная best practice для создания production-образов, напрямую влияющая на безопасность, стоимость хранения/передачи и скорость развертывания.

Ответ 18+ 🔞

А, слушай, вот эта штука с мультистейджингом в Docker — это вообще огонь, реально хитрая жопа, которая экономит кучу нервов. Представь себе обычную сборку: ты тулишь в образ всё подряд — компиляторы, исходники, тонны dev-зависимостей. Получается монстр, который жрёт места овердохуища, а в production тащит за собой кучу дыр, как будто на дворе 2002-й год. Доверия к такому образу — ноль ебать.

А тут придумали, ёпта, гениальную схему. Берёшь один Dockerfile, но делишь его на этапы, как будто два разных образа. Первый этап — это твой рабочий цех, полный грязи, шума и инструментов. Там ты компилируешь, собираешь, делаешь всю чёрную работу. А второй этап — это уже чистая, стерильная комната, куда ты заносишь только готовый продукт, вымыв руки. И из этой комнаты ты уже отправляешь товар покупателю.

Вот смотри на примере для Go, тут всё наглядно как божий день:

# --- ЭТАП 1: Сборочный цех (Builder) ---
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download  # Скачал зависимости один раз и закэшировал
COPY . .
# Собираем статический бинарник, чтобы он ни от кого не зависел
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /myapp .

# --- ЭТАП 2: Чистая комната (Runtime) ---
# Берём абсолютно пустой образ 'scratch' — это как голый линукс
FROM scratch AS runtime
# И ВОТ МАГИЯ: копируем ТОЛЬКО готовую программу из цеха 'builder'
COPY --from=builder /myapp /myapp
# Ну и SSL-сертификаты подкинем, без них сейчас никуда
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Для безопасности запускаем от простого юзера, не от рута
USER 1000:1000
ENTRYPOINT ["/myapp"]

И что получается в итоге? В первом образе у тебя целый Go со всеми пакетами, а во втором — только один бинарный файл myapp да пачка сертификатов. Разница в размере — просто ни хуя себе! С гигабайта до каких-то 10-20 мегабайт. Представляешь, как быстро это качается и запускается?

С точки зрения девопса, это просто песня:

  1. Безопасность. В продакшене у тебя лежит образ scratch. В нём нет шелла, нет пакетного менеджера, нихуя нет. Злоумышленнику просто некуда присесть, даже ls не выполнить. Поверхность для атаки — хуй с горы.
  2. Скорость и экономия. Ты не таскаешь по сети тонны ненужного хлама. Pull, push, деплой — всё летает. В registry места меньше занимает, денег меньше стоит.
  3. Удобство в CI/CD. В пайплайне ты можешь на первом этапе (builder) запустить все тесты, линтеры, проверить код. А если всё ок — собрать из него чистый рантайм-образ и уже его пихать в кластер. Один Dockerfile на все случаи жизни.

Короче, если ты до сих пор не используешь мультистейджинг для продакшена, то ты, прости, ебанько. Это must have, без вариантов. Экономит время, деньги и спасает от лишних волнений, ебать.