📝 Docker

Multi-stage builds: уменьшаем Docker-образ

P
Автор
Pyland
📅
Опубликовано
30.06.2026
⏱️
Время чтения
3 мин
👁️
Просмотров
80
🌳
Уровень
Продвинутый
🐦 💼 ✈️

Большой образ — это медленная загрузка, больше места на диске и большая поверхность атаки. Multi-stage builds решают эту проблему.

Проблема: образ тащит лишнее

Типичный Dockerfile для Python-приложения:

FROM python:3.12
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]

Образ python:3.12 весит ~1 ГБ. В нём — компилятор, заголовочные файлы, весь toolchain. В рантайме всё это не нужно. Нужен только итоговый код.

Идея Multi-stage builds

Разделить сборку на этапы:

  1. Builder stage — устанавливаем зависимости, компилируем, готовим артефакты
  2. Runtime stage — копируем только готовый результат в минимальный образ

Промежуточные образы не попадают в финальный результат.

Синтаксис

# Этап 1: сборка
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --prefix=/install -r requirements.txt

# Этап 2: рантайм
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
CMD ["python", "main.py"]

Ключевые конструкции:
- FROM ... AS builder — даём имя этапу
- COPY --from=builder — копируем файлы из другого этапа
- Можно ссылаться по имени этапа или по его номеру (--from=0)

Пример для Python приложения

Приложение с компилируемыми зависимостями (например, psycopg2, Pillow):

# Этап сборки: нужен компилятор для C-расширений
FROM python:3.12 AS builder
WORKDIR /build

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# Финальный образ: без компилятора
FROM python:3.12-slim
WORKDIR /app

# Копируем установленные пакеты
COPY --from=builder /install /usr/local

# Копируем только исходный код
COPY src/ ./src/
COPY main.py .

# Создаём непривилегированного пользователя
RUN adduser --disabled-password --no-create-home appuser
USER appuser

CMD ["python", "main.py"]

Разница в размере:

python:3.12          → ~1.0 ГБ (базовый образ)
python:3.12 + deps   → ~1.3 ГБ (с зависимостями)
python:3.12-slim + deps → ~200 МБ (multi-stage результат)

Пример для Node.js: сборка фронтенда

Node.js-проекты часто компилируют TypeScript или бандлят React. В рантайме нужен только dist/ и node_modules (только prod).

# Этап 1: установка всех зависимостей и сборка
FROM node:20-alpine AS builder
WORKDIR /app

COPY package*.json .
RUN npm ci                          # устанавливаем все зависимости

COPY . .
RUN npm run build                   # компилируем TypeScript / бандлим

# Этап 2: только prod-зависимости
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json .
RUN npm ci --omit=dev               # только production

# Этап 3: финальный образ
FROM node:20-alpine
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

USER node
CMD ["node", "dist/index.js"]

Для фронтенда, который раздаётся через nginx:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build                   # результат в /app/dist

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

Финальный образ — только nginx + статика. Node.js отсутствует полностью.

Размер:

node:20-alpine + devDeps + build → ~800 МБ
nginx:alpine + static dist       → ~25 МБ

Реальные размеры до и после

Приложение До После
Python API (с psycopg2) 1.3 ГБ 190 МБ
React SPA 850 МБ 25 МБ
Node.js API 600 МБ 120 МБ
Go сервис 800 МБ 12 МБ

Go — отдельная история: статический бинарник копируется в scratch (пустой образ):

FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .

FROM scratch
COPY --from=builder /app/server /server
CMD ["/server"]
# Размер образа: ~8 МБ

Выборочная сборка этапов

# Собрать только до конкретного этапа
docker build --target builder -t myapp:builder .

# Полезно для отладки: войти в builder-образ
docker run -it myapp:builder sh

Итог

Multi-stage builds — стандарт для production-образов. Меньший образ:
- быстрее скачивается и деплоится
- занимает меньше места в registry
- имеет меньшую поверхность атаки (нет компилятора, отладчиков)

Ваша реакция на статью

💬 Комментарии (0)

🔐 Войдите в систему, чтобы оставить комментарий
🚪 Войти
💭

Комментариев пока нет

Станьте первым, кто поделится мнением об этой статье!

🔗 Похожие

Похожие статьи

Продолжите изучение с этими материалами

📝

Деплой FastAPI с Docker

Или Railway автоматически найдёт Dockerfile.

📅 30.06.2026 👁️ 83
📝

Docker Compose: продвинутые возможности

Базовый docker-compose.yml — это старт. Для production нужны healthcheck, профили, override-файлы и управление ресурсами.

📅 30.06.2026 👁️ 79
📝

Docker Networking: как контейнеры общаются

Контейнеры изолированы, но часто должны взаимодействовать друг с другом и с внешним миром. Docker решает...

📅 30.06.2026 👁️ 82

Понравилась статья?

Подпишитесь на наши обновления и получайте новые статьи первыми. Развивайтесь вместе с PyLand!