Docker: البناء متعدد المراحل

آخر تحديث: 2026-08-26

تتقلص الصورة من 1.2 جيجابايت إلى 12 ميجابايت — البناء متعدد المراحل هو التقنية الأكثر فعالية لتقليل حجم الصورة.

1. ما ستتعلمه



2. قصة حقيقية لمطور Go

(1) نقطة الألم: صورة تطبيق بحجم 1.2 جيجابايت

صورة تطبيق Go الخاص بأليس حجمها 1.2 جيجابايت — لأنها استخدمت golang:1.22 كصورة أساسية، والتي تتضمن مترجم Go الكامل وكود المصدر وسلسلة الأدوات. لكن بيئة الإنتاج تحتاج فقط إلى ملف ثنائي مترجم بحجم 12 ميجابايت. كل نشر يستغرق 5 دقائق لتنزيل الصورة، ويتباطأ الخادم السحابي بنطاق ترددي 50 ميجابايت في كل مرة يتم فيها إصدار.

(2) نهج البناء متعدد المراحل

علمها بوب استخدام البناء متعدد المراحل: "في المرحلة الأولى، استخدم golang:1.22 للترجمة؛ في المرحلة الثانية، انسخ الناتج المترجم من Alpine."

DOCKERFILE
# المرحلة 1: البناء
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o server

# المرحلة 2: التشغيل (الملف الثنائي فقط، بدون مترجم Go)
FROM alpine:3.19
COPY --from=builder /src/server /usr/local/bin/
CMD ["server"]

(3) الفائدة: تم تقليل حجم الصورة من 1.2 جيجابايت إلى 12 ميجابايت

تم تقليل حجم الصورة بمقدار 100 مرة، وانخفض وقت التنزيل والنشر من 5 دقائق إلى 5 ثوانٍ، وتم تقليل الثغرات الأمنية من أكثر من 200 إلى 10 (المترجم غير مضمن في صورة الإنتاج).



3. مبادئ البناء متعدد المراحل

(1) مقارنة البناء أحادي المرحلة مقابل متعدد المراحل

100%
graph TB
    subgraph Single["بناء أحادي المرحلة"]
        S1["golang:1.22<br/>أساسي 800 ميجابايت"] --> S2["go build<br/>ناتج الترجمة"] --> S3["الصورة النهائية<br/>1.2 جيجابايت<br/>(تشمل مترجم + كود مصدري)"]
    end
    subgraph Multi["بناء متعدد المراحل"]
        M1["المرحلة 1: golang:1.22<br/>800 ميجابايت (مهملة)"] -->|"COPY --from"| M3["المرحلة 2: alpine:3.19<br/>أساسي 7 ميجابايت"]
        M1 --> M2["go build<br/>ناتج الترجمة"]
        M2 -->|"نسخ الملف الثنائي فقط"| M3
        M3 --> M4["الصورة النهائية<br/>12 ميجابايت<br/>(الملف الثنائي فقط)"]
    end

(1) الآلية الأساسية

المرحلة الغرض المحتويات الصورة النهائية
مرحلة البناء الترجمة/البناء مترجم، كود مصدري، أدوات بناء ❌ مهملة
مرحلة التشغيل التنفيذ نسخ النواتج الضرورية فقط ✅ محتفظ بها
📌 نقطة أساسية: البناء متعدد المراحل لا يضغط الصورة؛ بل يمنع العناصر غير الضرورية من التضمين في الصورة من البداية. جميع طبقات مرحلة البناء مهملة، وفقط طبقات مرحلة التشغيل تدرج في الصورة النهائية.



4. التطبيق العملي للبناء متعدد المراحل

▶ مثال: البناء متعدد المراحل في Go (الصعوبة: ⭐⭐)

DOCKERFILE
# ============================================
# المرحلة 1: ترجمة الملف الثنائي Go
# ============================================
FROM golang:1.22 AS builder

WORKDIR /src

# نسخ ملفات التبعيات أولاً (تحسين التخزين المؤقت)
COPY go.mod go.sum ./
RUN go mod download

# نسخ الكود المصدري والترجمة
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server

# ============================================
# المرحلة 2: إنشاء صورة تشغيل مصغرة
# ============================================
FROM alpine:3.19

# تثبيت شهادات CA (لطلبات HTTPS)
RUN apk --no-cache add ca-certificates

# نسخ الملف الثنائي المترجم فقط من مرحلة البناء
COPY --from=builder /src/server /usr/local/bin/server

# التشغيل كمستخدم غير root
RUN adduser -D -u 1000 appuser
USER appuser

EXPOSE 8080
CMD ["server"]
BASH
# بناء الصورة متعددة المراحل
docker build -t go-app:1.0 .

# التحقق من حجم الصورة النهائية
docker images go-app:1.0
💻 المخرجات:

TEXT 📖 للعرض فقط
REPOSITORY   TAG   IMAGE ID       SIZE
go-app       1.0   a1b2c3d4e5f6   12.5MB

▶ مثال: البناء بـ Node.js → التشغيل بـ Nginx (الصعوبة: ⭐⭐⭐)

مشاريع الواجهة الأمامية مثل React و Vue: الملفات الثابتة تُولد أثناء مرحلة البناء، ويستخدم Nginx لخدمة طلبات HTTP أثناء وقت التشغيل.

DOCKERFILE
# ============================================
# المرحلة 1: بناء تطبيق React
# ============================================
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ============================================
# المرحلة 2: الخدمة بـ Nginx
# ============================================
FROM nginx:1.25-alpine

# نسخ الأصول المبنية من مرحلة البناء
COPY --from=builder /app/build /usr/share/nginx/html

# إعدادات Nginx مخصصة لتوجيه SPA
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
💡 نصيحة: البناء متعدد المراحل أكثر فعالية لمشاريع الواجهة الأمامية — node_modules قد تكون 500+ ميجابايت، لكن ناتج البناء (HTML/CSS/JS) عادة يكون 5–20 ميجابايت فقط.

▶ مثال: استنساخ بيئة Python الافتراضية (الصعوبة: ⭐⭐)

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

DOCKERFILE
# المرحلة 1: إنشاء البيئة الافتراضية
FROM python:3.12 AS builder

WORKDIR /app
COPY requirements.txt .
RUN python -m venv /opt/venv && \
    /opt/venv/bin/pip install --no-cache-dir -r requirements.txt

# المرحلة 2: التشغيل بالبيئة الافتراضية فقط
FROM python:3.12-slim

WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
COPY . .

ENV PATH="/opt/venv/bin:$PATH"
CMD ["python", "app.py"]


5. اختيار صورة أساسية للتشغيل

(1) مقارنة صور ثلاث قواعد مصغرة

الصورة الأساسية الحجم تتضمن Shell تتضمن مدير حزم تتضمن libc حالات الاستخدام
scratch 0 ميجابايت ملفات Go/Rust الثنائية المترجمة بشكل ثابت
distroless 2–20 ميجابايت ✅ (glibc) من Google، آمنة للغاية
alpine 7 ميجابايت ✅ (ash) ✅ (apk) ✅ (musl) عند الحاجة إلى التصحيح

(2) ملاحظات حول صورة Scratch

DOCKERFILE
# Scratch: لا شيء على الإطلاق في الصورة
FROM scratch
COPY --from=builder /src/server /server
CMD ["/server"]
⚠️ ملاحظة: صورة scratch لا تتضمن shell أو ca-certificates أو بيانات المنطقة الزمنية. يجب ترجمة برامج Go بشكل ثابت CGO_ENABLED=0 للتشغيل على scratch. إذا كان برنامجك يتطلب طلبات HTTPS أو سجلات بطابع زمني، فمن الأكثر أمانًا استخدام alpine.



6. تقنيات متقدمة للبناء متعدد المراحل

▶ مثال: تسمية المراحل و --target لاختيار البناء (الصعوبة: ⭐⭐)

DOCKERFILE
# مراحل مسماة للوضوح
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o server

# بديل: مرحلة تطوير مع أدوات إعادة التحميل التلقائي
FROM golang:1.22 AS dev
WORKDIR /src
RUN go install github.com/cosmtrek/air@latest
COPY . .
CMD ["air"]

# الإنتاج: تشغيل مصغر
FROM alpine:3.19 AS prod
COPY --from=builder /src/server /usr/local/bin/
CMD ["server"]
BASH
# بناء مرحلة dev فقط
docker build --target dev -t myapp:dev .

# بناء مرحلة prod فقط
docker build --target prod -t myapp:prod .

▶ مثال: --target لتصحيح النواتج الوسيطة (الصعوبة: ⭐⭐⭐)

BASH
# بناء وفحص مرحلة البناء
docker build --target builder -t myapp:builder .

# تشغيل صورة البناء لتصحيح مشاكل البناء
docker run -it myapp:builder bash

# التحقق من الملف الثنائي المترجم
docker run --rm myapp:builder ls -la /src/server


7. مرجع أحجام نواتج الترجمة حسب اللغة

اللغة الصورة الأساسية ناتج البناء الصورة النهائية نسبة التخفيض
Go golang:1.22 (780 ميجابايت) ملف ثنائي ثابت 12 ميجابايت alpine: 12 ميجابايت 65x
Rust rust:1.75 (1.5 جيجابايت) ملف ثنائي ثابت 8 ميجابايت alpine: 15 ميجابايت 100x
Node.js node:20 (1.1 جيجابايت) build/ 5-20 ميجابايت nginx-alpine: 25 ميجابايت 44x
Java eclipse-temurin:21 (450 ميجابايت) JAR 30 ميجابايت jre-alpine: 170 ميجابايت 2.6x
Python python:3.12 (1 جيجابايت) venv/ 50 ميجابايت slim: 200 ميجابايت 5x


8. مثال كامل: بناء متعدد المراحل لتطبيق ويب Go

DOCKERFILE
# ============================================
# Dockerfile متعدد المراحل لتطبيق ويب Go
# المرحلة 1: بناء مع تخزين مؤقت للتبعيات
# المرحلة 2: تشغيل Alpine مصغر
# ============================================

# ---------- المرحلة 1: البناء ----------
FROM golang:1.22-alpine AS builder

# تثبيت git (لـ go mod download مع المستودعات الخاصة)
RUN apk add --no-cache git

WORKDIR /src

# التخزين المؤقت: نسخ ملفات التبعيات أولاً
COPY go.mod go.sum ./
RUN go mod download

# نسخ الكود المصدري
COPY . .

# بناء ملف ثنائي ثابت (CGO_ENABLED=0 لـ scratch/alpine)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-w -s" -o /app/server

# ---------- المرحلة 2: التشغيل ----------
FROM alpine:3.19

# تثبيت تبعيات وقت التشغيل
RUN apk --no-cache add ca-certificates tzdata

# إنشاء مستخدم غير root
RUN adduser -D -u 1000 appuser

WORKDIR /app

# نسخ الملف الثنائي فقط من مرحلة البناء
COPY --from=builder /app/server .

# تعيين الملكية
RUN chown -R appuser:appuser /app

USER appuser

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
  CMD wget -qO- http://localhost:8080/health || exit 1

CMD ["/app/server"]
BASH
# بناء
docker build -t go-web:1.0 .

# تشغيل
docker run -d -p 8080:8080 --name go-web go-web:1.0

# التحقق من الحجم والصحة
docker images go-web:1.0
docker inspect go-web --format='Size: {{.Size}}, Health: {{.State.Health.Status}}'
💻 المخرجات:

TEXT 📖 للعرض فقط
REPOSITORY   TAG   SIZE
go-web       1.0   15.2MB

Size: 15974400, Health: healthy

❓ أسئلة شائعة

س هل يؤثر البناء متعدد المراحل على سرعة البناء؟
ج ليس له تأثير كبير. طبقات مرحلة البناء لا تزال مخزنة مؤقتًا — إذا لم يتغير الكود، يستخدم go build التخزين المؤقت مباشرة. فقط الصورة النهائية لا تتضمن طبقات مرحلة البناء. سرعة البناء تقريبًا نفس سرعة البناء أحادي المرحلة، لكن حجم الصورة ينخفض بشكل كبير.
س هل يمكنني استخدام البناء متعدد المراحل لتصحيح الأخطاء؟
ج نعم. استخدم --target builder للبناء حتى مرحلة البناء، ثم استخدم docker run -it myapp:builder bash للدخول إلى مرحلة التصحيح. يمكنك أيضًا تعريف مرحلة dev في Dockerfile تتضمن أدوات تصحيح (مثل dlv أو air).
س كيف أصحح الأخطاء بدون shell في صورة scratch؟
ج نظرًا لأن صورة scratch لا تحتوي على shell، لا يمكنك docker exec للدخول إليها. طرق التصحيح: ① بناء صورة تصحيح باستخدام --target builder؛ ② تغيير FROM scratch مؤقتًا إلى FROM alpine للتصحيح؛ ③ استخدام docker cp على المضيف لنسخ ملفات السجل للخارج.
س هل يمكن تمرير الأسرار عبر البناء متعدد المراحل؟
ج لا تدرج الأسرار في مرحلة البناء — على الرغم من أن طبقة البناء لا تدرج في الصورة النهائية، فإن docker history لا يزال مرئيًا. استخدم --mount=type=secret (ميزة BuildKit) لتمرير الأسرار بأمان: RUN --mount=type=secret,id=ssh_key cp /run/secrets/ssh_key /root/.ssh/id_rsa.
س هل صورة distroless آمنة؟
ج إنها آمنة جدًا. صورة distroless التي تحتفظ بها Google لا تتضمن shell أو مدير حزم أو أي أدوات غير ضرورية، مما يؤدي إلى سطح هجوم صغير للغاية. ومع ذلك، التصحيح صعب (لعدم وجود shell)، لذا نوصي باستخدام Alpine للتطوير و distroless للإنتاج (مع تصحيح خارجي عبر السجلات والمراقبة).
س لماذا تستخدم صور Go Alpine بدلاً من Scratch؟
ج يوفر Alpine شهادات ca (مطلوبة لطلبات HTTPS) وبيانات المنطقة الزمنية (مطلوبة للطوابع الزمنية في السجلات)، بالإضافة إلى shell لتصحيح الطوارئ. الـ 5 ميجابايت الإضافية تستحق العناء. يستخدم Scratch فقط في سيناريوهات تتطلب تحسينًا شديدًا (مثل الأجهزة المضمنة).

📖 ملخص


📝 تمارين

  1. تمرين أساسي (الصعوبة: ⭐): قارن أحجام صور تطبيق Go المبنية باستخدام البناء أحادي المرحلة ومتعدد المراحل — استخدم أولاً FROM golang:1.22 لبناء أحادي المرحلة، ثم استخدم بناء متعدد المراحل، وسجل الفرق في الحجم بين الصورتين.
  2. تمرين متقدم (الصعوبة: ⭐⭐): ابنِ تطبيق React في مراحل متعددة: node:20-alpine بناء → nginx:1.25-alpine تشغيل. قارن أحجام الصورة النهائية لصورة Node.js الأساسية وصورة Nginx الأساسية.
  3. تحدٍ (الصعوبة: ⭐⭐⭐): استخدم docker history لتحليل حجم كل طبقة في صورة مبنية متعددة المراحل، واشرح لماذا لا تدرج طبقات مرحلة البناء في الصورة النهائية.
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%