Ответ
Я активно использую multi-stage сборки для создания минимальных и безопасных production-образов. Это стандартный подход в моих проектах.
Основная цель: отделить среду сборки (со всеми компиляторами, зависимостями для разработки) от финального рантайм-образа. Это резко уменьшает итоговый размер и площадь для потенциальных уязвимостей.
Пример для Go-приложения:
# STAGE 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 -ldflags="-s -w" -o main ./cmd/app
# STAGE 2: Final (Runtime)
FROM scratch
WORKDIR /
COPY --from=builder /app/main .
COPY --from=builder /app/configs ./configs
# Копируем SSL сертификаты, если нужно
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
CMD ["./main"]
Преимущества, которые я наблюдал на практике:
- Размер образа: Образ на основе
scratchилиalpineвесит несколько мегабайт вместо гигабайта (как образgolang:1.21). Это ускоряет загрузку и развёртывание. - Безопасность: В финальном образе нет компилятора, пакетного менеджера или shell (если используется
scratch), что минимизирует векторы для атаки. - Чистота: Образ содержит только скомпилированный бинарник и необходимые для его работы файлы (конфиги, сертификаты).
Нюансы и опыт:
- Можно использовать более двух стадий. Например, одна стадия для установки зависимостей, вторая для сборки, третья — финальная.
- Важно копировать из промежуточных стадий только нужные артефакты (бинарники, статические файлы).
- Для приложений на интерпретируемых языках (Python, Node.js) подход аналогичен: на одной стадии устанавливаются все
dev-зависимости и происходит сборка (например, минификация JS), а на финальную копируется только код и production-зависимости.