Вопрос проверяет понимание оптимизации Docker-образов и умение отделять “сборочное” окружение от “боевого”.
Multi-stage build — это сборка Docker-образа в несколько этапов, где на ранних этапах собираются зависимости и артефакты, а в финальный образ попадает только то, что нужно для запуска. Это уменьшает размер образа и снижает количество лишних инструментов внутри контейнера. Обычно на первом этапе ставят компиляторы и build-зависимости, а на последнем — только runtime. Такой подход ускоряет доставку и снижает поверхность атаки.
Multi-stage build — это техника, когда один Dockerfile содержит несколько “стадий” (этапов) сборки, а итоговый образ формируется из результатов предыдущих стадий. Идея простая: “строим тяжёлым образом, запускаем лёгким”.
Уменьшение размера образа
build-зависимости (gcc, make, headers) не попадают в production-образ
меньше слоёв с мусором и временными файлами
Повышение безопасности
в финальном контейнере меньше инструментов, которыми можно злоупотребить
меньше пакетов → меньше потенциальных уязвимостей
Ускорение доставки и деплоя
маленькие образы быстрее пушатся в registry и быстрее скачиваются на сервер
Разделение ответственности
отдельная стадия “сборка” и отдельная “рантайм” упрощают поддержку Dockerfile
Пример для Python-проекта, где есть зависимости, которые требуют сборки (условно psycopg, lxml, и т.п.). Суть: в builder ставим всё тяжёлое, а в runtime — только установленное.
# 1) Стадия сборки (builder)
FROM python:3.11-slim AS builder
WORKDIR /app
# build-зависимости (примерно)
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
# ... другие пакеты, если нужны ...
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt --prefix=/install
# 2) Финальная стадия (runtime)
FROM python:3.11-slim AS runtime
WORKDIR /app
# копируем только установленные зависимости
COPY --from=builder /install /usr/local
COPY . .
CMD ["python", "app.py"]
Что важно в этом примере:
builder содержит компиляторы и build-инструменты, но они не переходят в финальный образ
зависимости устанавливаются в отдельный путь (/install), который затем копируется в runtime
Python + зависимости, которые компилируются
сборка wheel/бинарников на этапе builder
перенос только wheels или готовой site-packages в runtime
Frontend + backend
на этапе builder собирается frontend (npm ci, npm run build)
в финальный образ копируется только dist/
backend остаётся лёгким
Генерация артефактов
миграции, схемы, статические файлы, бинарники CLI — всё можно собрать отдельно и копировать
“Случайно” тащат весь проект из builder
если сделать COPY --from=builder /app /app, можно перетащить мусор (кэш, временные файлы)
Ломают зависимости на runtime
если на builder стоит один набор системных библиотек, а в runtime их нет, бинарные зависимости могут не запуститься
решение: либо ставить нужные runtime-либы, либо использовать одинаковую базу (например, оба python:3.11-slim) и аккуратно добавлять только runtime-пакеты
Переиспользование кэша
порядок инструкций важен
обычно COPY requirements.txt и установка зависимостей делается до COPY ., чтобы при изменении кода не пересобирать зависимости
Multi-stage build стоит использовать, когда в сборке участвуют тяжёлые зависимости или отдельные артефакты (сборка пакетов, фронтенда, бинарников). Он помогает сделать production-образ меньше, безопаснее и проще в доставке.