یکی از رایج‌ترین چیزهایی که در ممیزی‌های DevOps می‌بینیم، ایمیج‌های چند صد مگابایتی — گاهی چند گیگابایتی — برای برنامه‌ای است که خودش چند مگابایت بیشتر نیست.

چرا ایمیج بزرگ مهم است

  • زمان استقرار: هر بار که یک گره جدید بالا می‌آید، باید کل ایمیج را بکشد.
  • هزینه: انتقال ایمیج بین رجیستری و کلاستر، پهنای باند واقعی مصرف می‌کند.
  • امنیت: هر بسته‌ای که در ایمیج نهایی هست، یک آسیب‌پذیری بالقوه است. کامپایلر، ابزار build و پکیج‌منیجر در محیط تولید هیچ کاری ندارند.

الگوی چندمرحله‌ای

ایده ساده است: در یک مرحله بسازید، و فقط خروجی را به مرحله بعد ببرید.

# ---- مرحله ساخت ----
FROM node:22-alpine AS builder
WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build && npm prune --omit=dev

# ---- مرحله اجرا ----
FROM node:22-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
RUN addgroup -S app && adduser -S app -G app

COPY --from=builder --chown=app:app /app/node_modules ./node_modules
COPY --from=builder --chown=app:app /app/dist ./dist
COPY --from=builder --chown=app:app /app/package.json ./

USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]

مرحله builder هر چیزی که لازم دارد نصب می‌کند. مرحله runner فقط سه چیز را برمی‌دارد: وابستگی‌های تولید، خروجی build و فایل package. کامپایلر، کش npm و سورس‌کد هیچ‌وقت وارد ایمیج نهایی نمی‌شوند.

چهار نکته که تفاوت واقعی می‌سازند

۱. ترتیب لایه‌ها را برای کش بچینید. COPY package*.json قبل از COPY . . می‌آید تا تا وقتی وابستگی‌ها عوض نشده‌اند، npm ci دوباره اجرا نشود.

۲. حتماً `.dockerignore` بنویسید. بدون آن، node_modules محلی و .git وارد context می‌شوند و همه چیز کند می‌شود:

node_modules
.git
.env*
dist
*.log

۳. با کاربر غیر root اجرا کنید. پیش‌فرض داکر root است. اگر مهاجم به کانتینر برسد، این تفاوت بین «دسترسی محدود» و «دسترسی کامل» است.

۴. نسخه را پین کنید. node:22-alpine بهتر از node:latest است، اما node:22.11-alpine قابل بازتولیدتر است.

اندازه‌گیری کنید

قبل و بعد را بسنجید:

docker images | grep my-app
docker history my-app:latest --human

دستور دوم نشان می‌دهد کدام لایه چقدر جا گرفته است. تقریباً همیشه یک لایه غافلگیرکننده وجود دارد که ۶۰ درصد حجم را برداشته.

گام بعدی

وقتی حجم را کم کردید، پویش آسیب‌پذیری را به خط لوله اضافه کنید. ابزارهایی مثل Trivy در چند ثانیه اجرا می‌شوند و می‌توانند build را روی آسیب‌پذیری بحرانی متوقف کنند — که دقیقاً همان جایی است که باید متوقف شود.