Ответ
Multistage builds в Docker — это подход, при котором в одном Dockerfile используется несколько инструкций FROM. Цель — отделить этап сборки (где нужны компиляторы, dev-зависимости) от этапа рантайма, скопировав в финальный образ только необходимые артефакты. Это радикально уменьшает размер и повышает безопасность образа.
Базовый пример для 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 -o /myapp ./cmd/app
# Этап 2: Финальный, минимальный образ
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Копируем только скомпилированный бинарник из этапа builder
COPY --from=builder /myapp .
CMD ["./myapp"]
В этом примере финальный образ основан на alpine (~5MB) и содержит только бинарник, а не весь Go-тулчейн (~400MB).
Пример для Python-приложения:
# Этап сборки
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-warn-script-location -r requirements.txt
# Финальный этап
FROM python:3.11-slim
WORKDIR /app
# Копируем установленные пакеты из домашней директории пользователя builder
COPY --from=builder /root/.local /root/.local
COPY . .
# Убедимся, что скрипты из pip доступны
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
Ключевые преимущества, которые я наблюдал на практике:
- Размер: Образы уменьшаются с гигабайтов до десятков мегабайт. Это ускоряет загрузку в registry и развёртывание в кластерах (Kubernetes).
- Безопасность: В продакшен-образе отсутствуют компиляторы, отладочные инструменты и исходный код, что сокращает поверхность для атаки.
- Чёткость:
Dockerfileявно разделяет зависимости для сборки и для выполнения. - Кэширование: Каждый этап кэшируется независимо, что ускоряет повторные сборки.