Оптимизация Docker-образов: multi-stage и не только
Образ на 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 за пару минут: 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 часто ускоряют пересборку, а финальный образ грузится быстрее.