أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Docker Multi-Stage Builds للمبتدئين: نزّل صورة Node.js من 1.2GB لـ 95MB

📅 ٢٧ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
Docker Multi-Stage Builds للمبتدئين: نزّل صورة Node.js من 1.2GB لـ 95MB

Docker Multi-Stage Builds للمبتدئين: نزّل صورة Node.js من 1.2GB لـ 95MB

مستوى المقال: مبتدئ. هذا الشرح موجّه لمن بنى Dockerfile واحد من قبل، ولاحظ أن صورة الإنتاج كبيرة بشكل غير منطقي مقارنة بحجم الكود الفعلي.

لو الـ image بتاع تطبيق Node.js عندك بيوصل 1.2GB وأنت كل اللي محتاجه شوية ملفات JavaScript جاهزة، Multi-Stage Builds بتنزّل الحجم لـ 95MB بتعديل في خمس سطور داخل Dockerfile. مفيش تغيير في الكود، ومفيش أداة جديدة، ومفيش CI خاص.

حاويات شحن متراصّة بألوان مختلفة كتشبيه لطبقات صورة Docker وفصل مرحلة البناء عن مرحلة التشغيل

المشكلة باختصار

الـ Dockerfile التقليدي بياخد كل أدوات البناء معاه للإنتاج. الـ npm cache، والـ devDependencies، والـ TypeScript compiler، وملفات الـ source قبل الترجمة. كل ده في الإنتاج بيشغّل مساحة، بيبطّأ الـ pull من الـ registry، وبيكبر سطح الهجوم الأمني.

الـ Multi-Stage Build بيفصل "مرحلة البناء" عن "مرحلة التشغيل" داخل نفس الـ Dockerfile. الناتج: صورة إنتاج فيها بس اللي محتاجه التطبيق وقت ما يشتغل، من غير الأدوات اللي ما عادش لها لزمة بعد البناء.

المثال البسيط: المطبخ والطبق

تخيّل مطعم بيقدّم بيتزا. المطبخ فيه فرن، عجّانة، خلاطات، أكياس دقيق، وثلاجات بمكونات خام. لمّا الزبون بيطلب بيتزا، النادل ما بياخدش معاه الفرن والعجانة للترابيزة. بياخد الطبق فقط.

الـ Dockerfile العادي بيوصّل الفرن والعجانة للترابيزة. الـ Multi-Stage Build بيوصّل الطبق فقط. نفس الأكل، تجربة أنظف، ومساحة أقل على الترابيزة.

التعريف الدقيق

Multi-Stage Build هو Dockerfile فيه أكثر من تعليمة FROM. كل FROM بيبدأ مرحلة (stage) جديدة بـ base image مختلف وله اسم اختياري بـ AS <name>. التعليمة COPY --from=<stage> بتنقل ملفات محددة من مرحلة لمرحلة تانية. الصورة النهائية اللي بتتنشر هي آخر مرحلة فقط، مع أي ملفات اتنقلت إليها بـ COPY --from. كل المراحل الوسطية بتتمسح من النتيجة النهائية.

قبل: Dockerfile بمرحلة واحدة

Dockerfile
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]

الحجم الناتج لتطبيق Express متوسط مع TypeScript و 40 dependency: حوالي 1.2GB.

المشكلة: الصورة فيها node_modules الكاملة بـ devDependencies (TypeScript, eslint, jest)، الـ source قبل الترجمة، الـ npm cache بحوالي 200MB، وكل أدوات أنظمة Debian اللي جوه image الـ node:20 الكامل.

بعد: Multi-Stage Build

Dockerfile
# المرحلة الأولى: البناء
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# المرحلة الثانية: الإنتاج
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node
CMD ["node", "dist/index.js"]

الحجم الناتج: حوالي 95MB. التحسّن الفعلي: 92% أقل. السطور اللي اتغيرت فعليًا: خمسة. الكود نفسه ما اتلمسش.

شاشة كود تعرض ملف Dockerfile بمرحلتين builder و runtime مع تعليمة COPY --from

إيه اللي حصل بالظبط

  1. المرحلة builder اشتغلت بـ node:20 الكامل علشان تقدر تبني الكود. التطبيق محتاج TypeScript و devDependencies في المرحلة دي بس.
  2. المرحلة runtime بدأت من node:20-alpine. الـ Alpine base image حجمه ~50MB مقابل ~370MB لـ node:20 الكامل.
  3. npm ci --omit=dev ثبّت dependencies الإنتاج فقط. لا TypeScript، لا jest، لا eslint.
  4. COPY --from=builder /app/dist ./dist نقل الكود المبني فقط. مفيش source أصلي، مفيش .git، مفيش node_modules بتاعة البناء.
  5. USER node منع التشغيل بصلاحيات root، وده default سيء في كثير من الـ images.

قياس الفرق

المقياسقبلبعد
حجم الصورة1.2GB95MB
زمن docker pull (شبكة 100Mbps)~96 ثانية~7 ثواني
عدد packages في الإنتاج1,847312
عدد CVEs مكتشفة بـ Trivy (تقديري)~140~12

القياس الحقيقي اللي يهم: لو بتعمل deploy 30 مرة في اليوم على 5 سيرفرات، الفرق بيوفّر تقريبًا 150 دقيقة pull يوميًا، وبيقلّل تكلفة egress من الـ registry بنسبة مماثلة. هذا الشرح مبني على فرضية إنك بتشغّل تطبيق Node.js قياسي بـ TypeScript. لو بتستخدم Bun أو Deno الأرقام بتختلف.

trade-offs لازم تعرفها

الـ Multi-Stage مش مجاني تمامًا.

  • زمن البناء أطول قليلاً أول مرة (15–20 ثانية إضافية تقريبًا، لأن فيه مرحلتين بدل واحدة). لكن BuildKit cache بيخلّي البناءات اللي بعد كده أسرع، وأحيانًا أسرع من الـ Dockerfile الأصلي بسبب layer caching أنظف.
  • Debugging أصعب على alpine. مفيش curl ولا vim ولا bash داخل الصورة النهائية. لو محتاج تدخل عليها، شغّل docker run --rm -it <image> sh أو ضيف RUN apk add --no-cache curl في مرحلة debug منفصلة.
  • مكتبات native (مثل bcrypt أو sharp) ممكن تحتاج build-base و python3 في مرحلة البناء على alpine، وأحيانًا تحتاج تنقل الـ .node binaries يدويًا للـ runtime.
  • الـ Alpine بيستخدم musl libc بدل glibc. ده بيكسر بعض المكتبات النادرة. لو حصل، استخدم node:20-slim بدل node:20-alpine — أكبر شوية (~80MB) لكن أقل مفاجآت.

متى لا تستخدم Multi-Stage

لو التطبيق فيه interpreter بدون build step (Python بدون Cython، Ruby بدون extensions، PHP خام)، الفائدة محدودة جدًا. ساعتها استخدم base image صغير مباشرة زي python:3.12-slim وخلاص. كذلك لو بتبني صورة CI runner أو dev container تستخدمها أنت بنفسك للتطوير، احتفظ بكل الأدوات داخلها — مفيش معنى توفّر 1GB في صورة هتعيش على لابتوبك.

كمان لو فريقك ما عندوش خبرة بـ Docker layers، ابدأ بحل أبسط: غيّر الـ base image لـ node:20-slim فقط. هتاخد 60% من الفائدة بسطر واحد، ومن غير تعقيد المرحلتين.

الخطوة التالية

افتح Dockerfile في أقرب مشروع Node.js عندك، وقسّمه لمرحلتين بالنمط فوق. شغّل docker build -t app:before . على النسخة القديمة، و docker build -t app:after . على الجديدة. قارن بـ docker images | grep app. لو الفرق أقل من 50%، غالبًا فيه node_modules بيتنقل بالغلط، أو الـ .dockerignore ناقصة. ضيف فيها node_modules و .git و dist قبل ما تعيد البناء.

المصادر

  • التوثيق الرسمي لـ Docker Multi-Stage Builds: docs.docker.com/build/building/multi-stage
  • Node.js Docker Best Practices (Node.js Foundation): github.com/nodejs/docker-node — BestPractices.md
  • Snyk: 10 best practices to containerize Node.js applications: snyk.io/blog
  • Alpine Linux base image — node official: hub.docker.com/_/node
  • Trivy container vulnerability scanner: aquasecurity.github.io/trivy

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة