Docker многоэтапные сборки Сжимайте изображение без нарушения развертывания
Практическое руководство по многоэтапным сборкам Docker — реальные размеры изображений до и после, рабочие примеры Dockerfile для Node.js и Python, и ошибки, которые встречаются в каждой команде хотя бы один раз.
Ваш образ Node.js составляет 1,1 ГБ. Вы добавили .dockerignore, удалили разработочные зависимости, попробовали node:slim — почти ничего не изменилось. Реальное решение — многоступенчатые сборки. Если вы ещё не переключились, вы отправляете компилятор TypeScript в производство.
Многоступенчатые сборки были в Docker с версии 17.05 (2017). Они используются недостаточно. Вот реальное объяснение: какие изменения, насколько они значимы, и три опасности, которые ловят команды при первом переходе.
Проблема одноступенчатой сборки
Большинство Dockerfile начинаются так:
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/server.js"]
Собираем и проверяем с помощью docker images: ~1,1 ГБ. Вы отправляете полный образ Node 20 с npm, вашу инфраструктуру разработки, все разработочные зависимости и полный исходный код. Ни одно из этого не работает в производстве — приложение просто требует скомпилированного dist/ результата и несколько пакетов на уровне выполнения.
Многоступенчатые сборки: решение
Каждый FROM инструкция запускает новую стадию с чистой файловой системой. Назовите стадии с помощью AS, затем используйте COPY --from=stagename для извлечения конкретных файлов в следующую стадию. Промежуточные стадии не попадают в конечный образ — они являются артефактами сборки, удаляются после завершения COPY сборки.
Вот тот же приложение в виде корректной многоступенчатой сборки:
# ---- Build stage ----
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- Runtime stage ----
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
Критическая строка: COPY --from=builder /app/dist ./dist. Это извлекает для декодирования любого компилированный результат из стадии сборки в образ на базе Alpine. Компилятор TypeScript, исходные файлы и разработочные зависимости никогда не попадают в конечный слой.
Результат: ~160 МБ вместо 1,1 ГБ. Это примерно 85% сокращение для типичного приложения на Node — и это то же самое скомпилированное изделие, просто без лишнего сопровождения.
Добавление стадии тестирования
Вы можете добавить стадию тестирования между сборкой и выполнением, чтобы запустить вашу тестовую систему. Если тесты проваливаются, сборка останавливается до создания образа выполнения. Если тесты проходят, вы полностью пропускаете стадию тестирования при сборке для производства.
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM builder AS tester
RUN npm test
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
В CI вы нацеливаете на стадию тестирования явно: docker build --target tester .. Для образов в производстве вы собираете без настройки цели и Docker запускает все стадии по порядку, останавливаясь на последней FROM. Стадия тестирования запускается, но её файловая система удаляется — тесты действуют как контрольный элемент, а не как нагрузка.
Python: аналогичная идея, немного иное выполнение
Многоступенчатые сборки для Python следуют той же схеме. Основное отличие: pip устанавливает пакеты в каталоге /root/.local при использовании --user, поэтому вы копируете этот каталог в образ на базе slim.
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "main.py"]
python:3.12 базовый образ: ~1 ГБ. python:3.12-slim с установленными зависимостями: ~180–250 МБ в зависимости от содержимого requirements.txt. Компилированные .pyc файлы идут бесплатно, поскольку они находятся рядом с исходным кодом.
Три опасности, которые ловят всех по крайней мере один раз
1. Копирование неправильных файлов
Наиболее распространённая ошибка: вы COPY --from=builder /app ./ вместо COPY --from=builder /app/dist ./dist. Вы просто скопировали всё — исходные файлы, тестовые фиксатуры, node_modules и т.д. — в ваш «минимальный» образ выполнения. Теперь он больше, чем в одноступенчатой версии.
Будьте чёткими относительно того, что вы копируете. Копируйте только каталог или файлы, которые действительно нужны для точки входа в производство. Для большинства приложений на Node: компилированный результат (dist/) и, при необходимости, любые статические ресурсы. Для Python: установленные пакеты и приложение, а не requirements.txt, не тесты, не ноутбуки.
2. Утечка секретов в слоях сборки
Если вы передаёте секреты как аргументы сборки (например, ARG NPM_TOKEN следуя за использованием в команде RUN ), этот секрет становится видимым во всех последующих слоях — даже в многоступенчатой сборке. docker history myimage покажет это.
Правильный подход — Docker BuildKit с --mount=type=secret:
RUN --mount=type=secret,id=npm_token NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
. Секрет монтируется только на уровне выполнения этого слоя — он никогда не попадает в историю образа. Собирайте с помощью: docker build --secret id=npm_token,src=.npmrc .
Популярный простой способ — удалить секрет в той же RUN стадии — не помогает в многоступенчатых сборках, но подход с BuildKit чище независимо.
3. Забывание о .dockerignore
Многоступенчатые сборки сокращают ваш конечный образ, но COPY . . в стадии сборки всё ещё отправляет ваш контекст на Docker daemon. Без .dockerignore, это включает .git/, node_modules/, тестовые фиксатуры, локальные .env файлы и любые секреты, хранящиеся в чистом виде. Стадия сборки видит всё это.
Минимальный .dockerignore для любого проекта на Node:
.git
node_modules
dist
.env
*.log
coverage
.nyc_output
Добавить .dockerignore в тот же день, когда вы добавляете Dockerfile. Размер контекста сборки отображается на первой строке docker build вывода (Sending build context to Docker daemon X MB) — если это число кажется слишком большим, проверьте, что включено.
Полезные инструменты для работы с Dockerfile
Если вы хотите начать с точки отсчёта до написания собственного, то Генератор Dockerfile на IO Tools создаст многоступенчатый Dockerfile для распространённых стеков. После того как вы напишете что-то, запустите его через Линтер и Форматер Dockerfile для обнаружения распространённых ошибок до того, как они попадут в CI — например, отсутствие WORKDIR, использование latest меток или выполнение под root.
Основной вывод
Многоступенчатые сборки — это двухэтапное изменение: добавьте именованную стадию сборки, скопируйте компилированный результат в новый минимальный образ. Снижение размера почти всегда оправдано — 80–90% типично для приложений на Node и Python. Основные трудности — слишком широкое использование COPY --from, утечка секретов как аргументов сборки и пропуск .dockerignore. Исправьте эти моменты и у вас будет образ, который действительно подходит для производства.
Вам также может понравиться
Установите наши расширения
Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска
恵 Табло результатов прибыло!
Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!
Подписаться на новости
все Новые поступления
всеОбновлять: Наш последний инструмент was added on Июн 26, 2026
