Docker multi-stage build: уменьшаем образ в 40 раз
Docker-образ вашего Go-приложения весит 900 МБ? Разбираем на практике, как с помощью multi-stage build уменьшить его до ~20 МБ, повысить безопасность и ускорить деплой.

Проблема
Многие пишут Dockerfile примерно так:
FROM golang:1.23
WORKDIR /app
COPY . .
RUN go build -o server .
CMD ["./server"]
Это работает. Но в итоговом образе остаются компилятор Go, кэш, исходный код и лишние утилиты. Размер легко превышает 900 МБ. К чему это приводит:
- Медленный деплой: каждый раз сотни мегабайт загружаются в registry и скачиваются на сервер.
- Большая поверхность атаки: каждый лишний пакет в образе — потенциальная уязвимость.
- Лишние расходы: диск, трафик и время CI тратятся впустую.
Решение: multi-stage build
Идея простая: на одном этапе мы собираем приложение, а на другом только запускаем готовый бинарник.
# Этап 1: сборка
FROM golang:1.23 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 server .
# Этап 2: запуск
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]
Результат: размер образа примерно 15–25 МБ.
Что изменилось?
AS builder: даём имя первому этапу, чтобы потом забрать из него файлы.go.modкопируется отдельно: зависимости меняются реже кода, поэтому Docker кэширует этот слой и сборка ускоряется.CGO_ENABLED=0: получаем статический бинарник без зависимости от внешних C-библиотек.-ldflags="-s -w": убираем отладочную информацию, бинарник становится меньше.distroless: внутри нет ни shell, ни пакетного менеджера. Только ваше приложение.USER nonroot: контейнер работает без прав root.
Примечание: в distroless нет shell, поэтому
docker exec -it ... shне сработает. Для отладки используйте вариантstatic-debian12:debug.
А для Python это работает?
Да. На этапе сборки собираем wheel-пакеты, а для запуска берём slim-образ:
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
COPY . .
CMD ["python", "main.py"]
Выигрыш здесь не такой впечатляющий, как в Go. Но компиляторы и инструменты сборки не попадают в итоговый образ, а это полезно и для размера, и для безопасности.
Итог
Multi-stage build — одна из самых простых оптимизаций: вы тратите 5 минут на Dockerfile и выигрываете время и безопасность при каждом деплое. Если в вашем проекте до сих пор один FROM, попробуйте сегодня и сравните вывод docker images.