Пошук уроків, статей та іншого контенту
Як розділити інструменти збірки й фінальний рантайм-образ, щоб не тягнути зайве в продакшн.
Збірка сучасного застосунку (компіляція TypeScript, збірка Next.js, транспіляція) часто потребує інструментів і залежностей (devDependencies, компілятори), які абсолютно не потрібні для того, щоб просто запустити вже зібраний застосунок у продакшені. Якщо зібрати образ звичайним, одноетапним Dockerfile, усі ці зайві інструменти й проміжні файли залишаються в фінальному образі — роздуваючи його розмір і поверхню потенційних вразливостей.
# Етап 1: збірка (усі інструменти й devDependencies тут — і залишаються тут)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Етап 2: фінальний образ — лише те, що реально потрібно для запуску
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/package.json /app/package-lock.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/server.js"]COPY --from=builder переносить лише конкретні, потрібні файли (результат збірки — папку dist) з проміжного етапу builder у фінальний образ — сам проміжний етап (з усіма його devDependencies, вихідним TypeScript-кодом, інструментами збірки) не потрапляє у фінальний образ узагалі й не впливає на його розмір.
Фінальний образ містить лише те, що реально потрібно для виконання: рантайм (Node.js), продакшн-залежності (без devDependencies), зібраний код — часто в кілька разів менший за розміром за наївний одноетапний варіант із тим самим Dockerfile, який просто ніколи не видаляв інструменти збірки.
Менший фінальний образ означає швидше розгортання (менше даних передавати мережею до сервера), менше місця на диску, і менша поверхня для вразливостей — компілятори й інструменти збірки, яких немає в фінальному образі, просто не можуть бути експлуатовані, навіть якщо в них знайдуть вразливість.
Одноетапний Dockerfile для застосунку, що потребує кроку збірки, — фінальний образ тягне devDependencies, вихідний нескомпільований код і інструменти збірки, яких насправді ніколи не використовує в продакшені.
Забути --omit=dev (чи еквівалент) при повторному npm ci у фінальному етапі — навіть у multi-stage build фінальний образ може непотрібно отримати devDependencies, якщо явно не виключити їх.
Копіювати цілі директорії з проміжного етапу (COPY --from=builder /app .) замість конкретних потрібних файлів — тягне у фінальний образ значно більше, ніж реально потрібно для запуску.
Multi-stage build дозволяє описати кілька послідовних етапів збірки в одному Dockerfile (кожен зі своїм FROM), де фінальний етап переносить через COPY --from=<етап> лише конкретні потрібні артефакти з попередніх — інструменти збірки й проміжні файли не потрапляють у фінальний образ узагалі. Результат — значно менший, безпечніший продакшн-образ, ніж наївний одноетапний Dockerfile для застосунку, що потребує кроку збірки.