MAATRIX / Блог / Оптимизация Docker-образов: multi-stage и не только

Оптимизация Docker-образов: multi-stage и не только

Блог MAATRIX · 2026-07-07

Образ на 1.2 ГБ и образ на 40 МБ делают одно и то же — но первый дольше грузится, дольше деплоится и занимает диск. Разберём, как ужать образ в разы без потери функциональности.

Почему размер образа важен

['Большой образ — это медленный docker pull при каждом деплое, лишний трафик, забитый диск и медленный старт контейнеров. На CI это выливается в минуты ожидания на каждом коммите. Уменьшение образа ускоряет весь цикл разработки и экономит место на сервере.', 'Даже на быстрых NVMe-дисках MAATRIX аккуратный образ выигрывает: меньше слоёв — меньше операций чтения при запуске десятков контейнеров.', 'Разберёмся, из чего складывается размер. Образ состоит из слоёв: каждая инструкция FROM, RUN, COPY добавляет свой слой поверх предыдущего. Слои не удаляются — если вы установили пакет в одном слое, а удалили в следующем, файлы всё равно останутся в истории образа и займут место. Поэтому оптимизация — это и про выбор базового образа, и про то, как написаны сами инструкции.']

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK и США. Оплата картой РФ и по СБП.

Арендовать VPS для сборки образов

Multi-stage сборка — главный приём

['Идея проста: собираем приложение в одном «жирном» образе со всеми компиляторами, а в финальный образ копируем только готовый бинарник или артефакт. Пример для Go:', '
FROM golang:1.23 AS build\nWORKDIR /src\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\nRUN CGO_ENABLED=0 go build -o /app ./cmd/server\n\nFROM alpine:3.20\nRUN apk add --no-cache ca-certificates\nCOPY --from=build /app /app\nENTRYPOINT ["/app"]
', 'Первый stage весит под гигабайт со всем тулчейном Go, финальный — считанные мегабайты. В образ попадает только бинарник, а не исходники и компилятор.', 'Для статически слинкованного Go-бинарника можно пойти ещё дальше и использовать пустой образ scratch — в нём вообще нет операционной системы, только ваш файл. Размер падает до размера самого бинарника. Правда, тогда придётся вручную положить сертификаты и часовые пояса, если приложение ходит по HTTPS.']

Тот же приём для Node.js

['Для интерпретируемых языков в финал кладём только зависимости и код, без dev-пакетов и кэша npm:', '
FROM node:20 AS build\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci\nCOPY . .\nRUN npm run build\n\nFROM node:20-alpine\nWORKDIR /app\nCOPY --from=build /app/dist ./dist\nCOPY package*.json ./\nRUN npm ci --omit=dev\nCMD ["node", "dist/index.js"]
', 'Использование npm ci вместо npm install даёт воспроизводимую сборку по lock-файлу, а --omit=dev выкидывает инструменты разработки. Часто именно dev-зависимости — линтеры, тестовые фреймворки, сборщики — раздувают node_modules в разы, и в рантайме они не нужны.']

.dockerignore и порядок слоёв

['Каждая инструкция в Dockerfile — это слой, который кэшируется. Копируйте зависимости до исходников: пока package.json не меняется, слой с установкой берётся из кэша, и пересборка идёт за секунды.', 'Обязательно добавьте .dockerignore, чтобы в контекст сборки не улетали лишние гигабайты:', '
.git\nnode_modules\ndist\n*.log\n.env\nDockerfile
', 'Без этого файла COPY . . тащит в образ и .git, и локальные node_modules — образ пухнет на пустом месте.']

Проверяем результат и чистим

['Смотрим размеры образов и историю слоёв:', '
docker images\ndocker history myapp:latest
', 'Регулярно убирайте мусор — висячие образы и кэш сборки съедают диск:', '
docker image prune -f\ndocker builder prune -f
', '
  • Не ставьте лишние пакеты — каждый apt/apk install остаётся в слое навсегда
  • Объединяйте RUN и чистите кэш в той же инструкции: apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
  • Берите -alpine или -slim варианты базовых образов, где это возможно
', 'Аккуратные образы особенно окупаются, когда вы деплоите часто: на VPS MAATRIX с ежедневными бэкапами лёгкий образ быстрее восстанавливается и разворачивается заново.', 'Подытожим правила хорошего Dockerfile: базовый образ — минимальный (alpine, slim, distroless или scratch); зависимости копируются и ставятся до исходного кода ради кэша; сборочные инструменты остаются в build-stage и не попадают в финал; временные файлы и кэш пакетных менеджеров удаляются в той же инструкции, где создаются; в контекст не летит ничего лишнего благодаря .dockerignore. Пять простых принципов, которые превращают гигабайтный образ в десятки мегабайт и ускоряют каждый деплой.']

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK и США. Оплата картой РФ и по СБП.

Арендовать VPS для сборки образов

Частые вопросы

Насколько реально уменьшить образ?

Типично в 5-20 раз. Go-приложение с 900 МБ ужимается до 15-40 МБ, Node-сервис — с 1 ГБ до 150-200 МБ через alpine и multi-stage.

Alpine всегда лучший выбор?

Обычно да, но musl libc иногда ломает нативные модули. Если возникают странные ошибки — берите -slim на базе Debian.

Multi-stage замедляет сборку?

Нет, наоборот. Кэш слоёв и раздельные stage часто ускоряют пересборку, а финальный образ грузится быстрее.