Дело о пропавших гигабайтах: расследуем Docker-образ Node.js

Пятница, 19:47. Джун-дев Макс получает тревогу: деплой длится 15 минут — образы весят 1,4 ГБ. Надо срочно найти, где прячутся лишние мегабайты, и похудеть контейнер.


Шаг 0. Собираем улики

  1. Устанавливаем dive — консольный профайлер контейнеров.
    brew install dive dive my-app:latest
    Tool показывает, какие команды добавили «мусор» и каков вес каждого слоя.
  2. Фиксируем исходную массу тела: 1 400 МБ.

Шаг 1. .dockerignore — первый кордон полиции

Новички часто пишут COPY . . и тянут в образ тесты, git-историю и кэш IDE. В файл .dockerignore добавляем:

node_modules
.git
*.log
coverage

Минус десятки мегабайт за один ход.


Шаг 2. Подозреваемый № 1 — node_modules

В проде dev-зависимости (ESLint, Jest) не нужны.
Меняем установку:

RUN npm ci --omit=dev

Экономия ≈ 300 МБ на среднем проекте.


Шаг 3. Каждый RUN — новый слой

Несколько RUN = несколько слоёв. Склеиваем команды:

RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*

Шаг 4. Перехват: Multi-Stage Build

Как это работает

Dockerfile содержит два этапа:

  1. Stage 1 — build: собираем приложение.
  2. Stage 2 — run: берём только артефакты и чистый runtime.

Минимальный рецепт для Node.js

# ---------- Stage 1: build ----------
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build # кладёт JS в /app/dist

# ---------- Stage 2: run ----------
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist .
CMD ["server.js"]

Размер образа падает до ≈ 240 МБ.


Шаг 5. Финальный удар: Distroless

Distroless-базы содержат только runtime-библиотеки, без bash и apt.
Это ещё −30 % веса и минимум CVE. Для отладки используйте теги *-debug или подключайте временный busybox.


Итоги расследования

ЭтапВес образа
До оптимизации1,4 ГБ
.dockerignore + npm ci --omit=dev650 МБ
Multi-Stage Build240 МБ
Distroless≈ 120 МБ

Прод катится за секунды, счёт за трафик падает, а Макс закрывает ноутбук с улыбкой: преступление лишних гигабайтов раскрыто.


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

Можно ли обойтись node:alpine вместо Distroless?
Да, получится ≈ 180 МБ и проще дебажить (apk add bash).

Как чистить кэш сборок?
docker builder prune -af удалит dangling-кэш и освободит гигабайты на ноутбуке.

Всегда ли убираем dev-зависимости?
Если у вас SSR или миграции Prisma в рантайме, dev-зависимости могут потребоваться. Тестируйте контейнер!

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *