2.2 · تحسين البناء : الطبقات والذاكرة المؤقتة والبناء متعدد المراحل

المستوى 2 · Dockerfile وبناء الصور

2.2تحسين البناء : الطبقات والذاكرة المؤقتة والبناء متعدد المراحل

الهدف: تقسيم زمن البناء وحجم الصور على 10 بفضل الذاكرة المؤقتة والبناء متعدد المراحل.
الوقت المقدّر: 14 دقيقة · قراءة ذاتية · إذا كان لديك 5 دقائق فقط: اقرأ «الأساسي في 30 ثانية» + المخطط.

الأساسي في 30 ثانية

  • الترتيب يقود الذاكرة المؤقتة : يعيد Docker تشغيل الطبقات حتى أول طبقة معدلة — الثابت أولا، والمتغير بعده.
  • متعدد المراحل = نبني ضخما ونسلّم نحيفا : نجمّع في مرحلة مجهزة، ولا نسلّم إلا القابل للتشغيل — COPY --from=build.
  • قِس قبل التحسين : يثبت docker images وdocker history الكسب — استهدف أقل من 200 Mo لتطبيق ويب.

حالة مستمرة — إدير والبناء الذي كان يعيد كل شيء

تطبيق إدير المغلف (2.1) يعمل، لكن كل فاصلة معدلة تعيد تشغيل npm install ويستمر البناء دقائق ؛ وتتجاوز الصورة الغيغابايت. أعاد إدير الترتيب : الاعتماديات الثابتة أولا (package*.json + التثبيت)، ثم الكود المتغير — فلم يعد تغيير الكود يعيد التثبيت. ثم انتقل إلى متعدد المراحل : مرحلة مجهزة تجمّع، ومرحلة slim لا تستقبل إلا النتيجة عبر COPY --from=build. والحكم بالأرقام : من 1 Go إلى نحو 150 Mo. وفي 2.3، سيرقّم إدير هذه الصورة النحيفة ؛ وفي 2.5 سيقوّيها.

تتمة الخيط الرابط 2.1 : تطبيق إدير أصبح سريع البناء ونحيف التسليم — ويستمر في 2.3 و2.4 و2.5.

1. الذاكرة المؤقتة : الترتيب يغيّر كل شيء

يعيد Docker تشغيل الطبقات بالترتيب ويعيد استخدام الذاكرة المؤقتة حتى أول طبقة معدلة : فكل ما بعدها يُعاد تشغيله. لذلك نضع الثابت أولا (الاعتماديات : COPY package*.json ثم التثبيت) والمتغير بعده (الكود : COPY . .). وعكس الترتيب يعيد تحميل كل شيء عند كل فاصلة معدلة. ينسخ إدير البيانات الوصفية ويثبّت قبل الكود : فتغيير الكود لا يعيد إلا الطبقات الأخيرة.

2. متعدد المراحل : نبني ضخما ونسلّم نحيفا

نجمّع في صورة مجهزة (مرحلة « build » بعدة تطوير كاملة) ثم ننسخ النتيجة وحدها في صورة تشغيل خفيفة (COPY --from=build). فتبقى عدة التطوير والمصادر والذاكرات المؤقتة على الرصيف : ولا تحتوي الصورة النهائية إلا الضروري للتشغيل. وكل FROM تفتح مرحلة ؛ ولا نسلّم إلا الأخيرة. يبني إدير في صورة كاملة ويسلّم في node:20-slim.

3. قِس قبل التحسين

يعطي docker images الحجم، ويعطي docker history الوزن لكل طبقة : فنحسّن ما نقيسه. وهدف واقعي : تطبيق ويب تحت 200 Mo (قاعدة slim + متعدد المراحل). وملف .dockerignore كامل (node_modules و.git وdist) يخفف السياق المرسل إلى المحرك. وتحت العتبة، نطارد الاعتماديات غير الضرورية، لا البايتات. يدوّن إدير الحجم والمدة قبل/بعد : بالأرقام الداعمة، لا بالحدس.

خطأ شائع في الفهم: « COPY . . في السطر الأول أبسط ولا يغيّر شيئا ». خطأ : فأدنى تعديل يُبطل الذاكرة المؤقتة منذ الطبقة الأولى ويعيد كل شيء، بما فيه التثبيت. وقاعدة إدير : الاعتماديات الثابتة أولا (مخبأة)، ثم الكود المتغير — ولا نسلّم إلا القابل للتشغيل في متعدد المراحل.
بناء سريع ونحيف الاعتماديات أولا (الذاكرة المؤقتة)، ثم الكود، وتسليم نحيف الاعتماديات (الذاكرةالمؤقتة) الثابت أولا، والتثبيت مخبأالكود (المتغير) يُعدَّلغالبا، ويُعاد وحدهتشغيل نحيف متعددالمراحل وslim ومقاس
الاعتماديات أولا (الذاكرة المؤقتة)، ثم الكود، وتسليم نحيف
نص المخطط (SEO / قارئات الشاشة / الترجمة): 1) يرتّب إدير : البيانات الوصفية والتثبيت أولا (طبقات مخبأة)، ثم الكود — فلم يعد تغيير الكود يعيد npm install. 2) يفصل : مرحلة build مجهزة للتجميع، ومرحلة slim لا تستقبل إلا النتيجة عبر COPY --from=build. 3) يقيس : docker images وdocker history قبل/بعد، والسياق مخفف عبر .dockerignore. الدرس : الترتيب يصنع السرعة، ومتعدد المراحل يصنع الحجم، والقياس يثبت الاثنين.

هل تعلم؟

رأى إدير مسبقا بناء يعيد تثبيت 400 Mo من الاعتماديات لأجل فاصلة أُزيحت في تعليق — COPY . . في السطر الأول.
صحيح : فسطر في غير محله قد يكلّف دقائق عند كل بناء.

ترتيب الأسطر هو وقت حياة.

🔎 مثال ملموس — 🔎 مثال ملموس — Node : من 1 Go إلى 150 Mo

ملف Dockerfile الخاص بإدير، بنسخة محسنة ومقاسة :

# المرحلة 1 : البناء
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# المرحلة 2 : التنفيذ الخفيف
FROM node:20-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["node", "dist/server.js"]

القراءة : تبقى عدة التطوير والمصادر في مرحلة البناء، المهملة بعدها ؛ ولا تحتوي المرحلة النهائية إلا القابل للتشغيل.

الدرس : يثبت docker history أي الطبقات ذابت — قِس قبل التصفيق.

مفردات القسم

الذاكرة المؤقتة للبناء (Cache)
إعادة استخدام الطبقات غير المعدلة بين بناءين : فيُعاد كل شيء انطلاقا من أول طبقة معدلة.

مثال بسيط: لم يعد إدير يعيد npm install عندما يتغير كوده وحده.

ما ليس كذلك: صورة نهائية أصغر.

رد فعل: الثابت أولا للتخبيء.

البناء متعدد المراحل (Multi-stage)
عدة تعليمات FROM : نبني في مرحلة مجهزة ولا نسلّم إلا المرحلة الأخيرة النحيفة.

مثال بسيط: يجمّع إدير في node:20 ثم يسلّم في node:20-slim.

ما ليس كذلك: مجرد إعادة ترتيب للطبقات.

رد فعل: نبني ضخما ونسلّم نحيفا.

COPY --from
النسخ من مرحلة سابقة (لا من السياق) : فلا نسترجع إلا النتيجة المجمّعة.

مثال بسيط: ينسخ إدير /app/dist من مرحلة البناء إلى المرحلة slim.

ما ليس كذلك: COPY كلاسيكي من السياق.

رد فعل: لا تسلّم إلا القابل للتشغيل.

history
تفصيل صورة طبقة بطبقة (docker history) : وزن كل طبقة ومصدرها.

مثال بسيط: يقارن إدير history قبل/بعد ليثبت الكسب.

ما ليس كذلك: الحجم الإجمالي وحده (docker images).

رد فعل: قِس لكل طبقة قبل التحسين.

في التطبيق — عمليا — صهّر صورة

  1. ابنِ تطبيقك « الساذج » (كله في كتلة واحدة) ودوّن الحجم + المدة.
  2. أعد الترتيب : الاعتماديات أولا، ثم الكود ؛ وأعد البناء بعد تغيير الكود.
  3. حوّل إلى متعدد المراحل (بناء مجهّز، وتشغيل slim) وقِس مجددا.
  4. قارن history قبل/بعد واحتفظ بالوصفة الرابحة.
بناءاتك سريعة وصورك نحيفة، بالأرقام الداعمة.

نص جاهز — برّر التحسين (30 ثانية)

« قبلا، كان كل تعديل يعيد كل التثبيت وكانت الصورة تتجاوز الغيغابايت. والآن الاعتماديات مخبأة، ولا يُعاد إلا الكود، ومتعدد المراحل لا يسلّم إلا القابل للتشغيل : نحو 150 Mo. والدليل : docker images وhistory قبل/بعد. أسرع في البناء، وأخف في النشر. »

يُقال مع عرض قياسي إدير، بالأرقام الداعمة.

قالب جاهز للنسخ — Dockerfile متعدد المراحل لتطبيق Node

FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["node", "dist/server.js"]

ذاكرة ميتة

COPY . . في السطر الأول : كل بناء يعيد كل شيء — فيعيد إدير الاعتماديات قبل الكود.

عدة مسلّمة

صورة إنتاج بعدة التطوير الكاملة : ينتقل إدير إلى متعدد المراحل ولا يسلّم إلا dist.

سياق ثقيل

سياق 800 Mo : يكتب إدير .dockerignore (node_modules و.git وdist) فيتنفس البناء.

تحسين عمياني

« أخف، أحس بذلك » : فيُخرج إدير docker history ويثبت الكسب طبقة بطبقة.

✓ ممارسات جيدة

  • الاعتماديات أولا، ثم الكود، دائما.
  • .dockerignore كامل (node_modules و.git وdist).
  • سلّم في slim، وابنِ في الكاملة.

✗ أخطاء شائعة

  • COPY . . في السطر الأول (ذاكرة ميتة).
  • تسليم عدة التطوير في الإنتاج.
  • التحسين دون قياس (history/images).

النقاط الأساسية

  • ترتيب الطبقات يقود الذاكرة المؤقتة (الثابت أولا، والمتغير بعده).
  • متعدد المراحل : لا نسلّم إلا القابل للتشغيل (COPY --from=build).
  • نقيس : docker images (الحجم) + docker history (لكل طبقة).
  • .dockerignore كامل : فلا يجب أن يحمل السياق غير الضروري.

تحدي 48 ساعة — طبِّق غدا

على تطبيقك المغلف (2.1) : دوّن الحجم + المدة، وأعد ترتيب الاعتماديات ثم الكود، وحوّل إلى متعدد المراحل، ثم قارن history قبل/بعد.

قائمة الخروج من القسم
  • أستطيع ترتيب Dockerfile لأجل الذاكرة المؤقتة (الثابت ثم المتغير).
  • أعرف كتابة بناء متعدد المراحل لا يسلّم إلا القابل للتشغيل.
  • أقيس عبر images + history قبل إعلان أي كسب.
تقييم ذاتي 1-5 : هل يأتي COPY . . عندي بعد التثبيت؟هل تحتوي صورتي النهائية شيئا غير القابل للتشغيل؟

أسئلة شائعة

لماذا يعيد بنائي npm install في كل مرة؟

لأن COPY . . يسبق RUN npm ci : فأدنى ملف معدَّل يُبطل الذاكرة المؤقتة منذ تلك الطبقة. انسخ package*.json وثبّت قبل الكود : فلن يعيد التثبيت إلا تغيير الاعتماديات.

هل يبطئ متعدد المراحل البناء؟

قليلا (مرحلتان للبناء)، لكنه يقسّم الحجم المسلَّم وسطح الهجوم. وفي CI مع الذاكرة المؤقتة، تكون الكلفة الإضافية هامشية أمام الكسب في النشر والأمان.

تحقّق من فهمك

لبناء سريع، أين تضع COPY . . (الكود)؟

موارد إضافية (اختياري، للتعمّق أكثر)

اختبار المستوى 2أسئلة جديدة وأصعب للتحقق من مكتسباتك