المستوى المطلوب: متوسط. بيفترض المقال إنك بتعرف تكتب Dockerfile بسيط وتشغّل
docker build. لو مبتدئ تمامًا، هتلاقي كل مفهوم متشروح بمثال قبل التفاصيل التقنية.
سطرين في نهاية الـ Dockerfile بيقدروا ينزّلوا صورة خدمتك من ~850 ميجابايت إلى ~18 ميجابايت، من غير ما تلمس سطر واحد في كود التطبيق. النتيجة: نشر أسرع، سحب أخف على كل عقدة، وسطح هجوم أصغر بكتير.
ليه صورة Docker بتتضخّم وإزاي تصغّرها بالـ Multi-Stage Build
المشكلة باختصار
الصورة الضخمة مش مجرد رقم وحش في docker images. هي بتكلّفك في تلات حتات: كل deploy بيسحب جيجابايتات على كل عقدة فيبطّأ الـ rollout، تخزين الصور في الـ registry بيتراكم ويكلّف فلوس، وأهم حاجة: كل أداة بناء وكل مكتبة تطوير بتفضل جوه الصورة النهائية هي باب محتمل لثغرة أمنية. الصورة اللي فيها compiler وgit وحزم dev هي هدف أكبر من صورة فيها ملف تنفيذي واحد بس.
ليه الصورة بتكبر أصلًا؟ الطبقات هي السبب
خلّينا نبدأ بمثال بسيط. تخيّل طاهي بيحضّر طبق واحد. عشان يجهّزه استعمل مطبخ كامل: أفران، خلاطات، عشرات التوابل، وأكياس دقيق. لما يقدّم الطبق للزبون، هو مش بيديله المطبخ كله بأدواته وفوضاه، بيديله الطبق النهائي في صحن نظيف بس. الصورة أحادية المرحلة (single-stage) بتعمل العكس: بتحزم المطبخ كله مع الطبق.
علميًا: صورة Docker مبنية من طبقات (layers) فوق بعض. كل تعليمة RUN وCOPY وADD في الـ Dockerfile بتنتج طبقة جديدة تتراكم على اللي قبلها. لو بنيت تطبيق Go بصورة golang:1.22، الصورة دي لوحدها فيها الـ Go toolchain كامل (compiler، أدوات، مكتبات) بحجم يقارب 800 ميجابايت. بعد ما تبني، الملف التنفيذي بتاعك ممكن يكون 15 ميجابايت بس، لكن الـ 800 ميجا بتاعة أدوات البناء بتفضل معاك في الطبقات للأبد. انت بتشحن المطبخ عشان تقدّم طبق.
الحل: افصل مرحلة البناء عن مرحلة التشغيل
الـ Multi-Stage Build بيخلّيك تكتب أكتر من مرحلة (stage) في نفس الـ Dockerfile. مرحلة أولى فيها كل أدوات البناء، ومرحلة أخيرة فاضية تقريبًا تنسخ منها الناتج النهائي بس. Docker بيرمي كل الطبقات الوسيطة ويحتفظ بالمرحلة الأخيرة فقط كصورة نهائية.
ده Dockerfile حقيقي لتطبيق Go، قابل للنسخ:
# المرحلة 1: البناء — فيها كل الأدوات
FROM golang:1.22 AS build
WORKDIR /src
# انسخ ملفات الاعتماديات الأول لوحدها
COPY go.mod go.sum ./
RUN go mod download
# بعدين انسخ الكود وابنِ
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/server ./cmd/server
# المرحلة 2: التشغيل — صورة شبه فاضية
FROM gcr.io/distroless/static-debian12
COPY --from=build /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]