المستوى: مبتدئ — يكفي إنك تعرف Dockerfile بسيط وتعرف تشغّل docker build.
Distroless Docker للمبتدئ: قلّل حجم الـ Image بنسبة 84% وامنع 98% من CVEs
لو الـ image بتاع تطبيق Node.js عندك حجمه 1.13GB وعليه 167 ثغرة CVE مكتشفة، انت مش محتاج تنقل لـ alpine ولا slim. انت محتاج Distroless. هنا هتعرف ليه Google بتشحن خدماتها على الصور دي من سنة 2017، وإزاي تحوّل تطبيقك في Dockerfile من 8 سطور، وإيه التمن الحقيقي اللي بتدفعه مقابل المكسب.
المشكلة باختصار
لما بتعمل docker build على تطبيق Node.js بالطريقة العادية، Docker بيحط معاك توزيعة Debian كاملة: bash و apt و curl و perl و حوالي 200 مكتبة C. التطبيق نفسه ممكن يكون 4 ميجا كود، بس الـ image النهائي بيطلع 1.13GB. السبب إنك بتشحن نظام تشغيل كامل علشان تشغّل برنامج واحد.
المشكلة مش بس الحجم. كل أداة جوّا الـ image هي بوابة لمهاجم محتمل. لو حد وصل لـ shell جوّا الكونتينر، هيلاقي curl يبعت بياناتك بره، و wget ينزّل malware، و bash يكتب exploits. ولو الأدوات دي مش موجودة أصلاً، المهاجم بيقف عند أول خطوة.
تشبيه قبل التعريف العلمي
تخيل إنك بتشحن منتج صغير من مصنعك للعميل. الطريقة العادية: تحطّ المنتج في صندوق فيه برضو كتالوج المنتجات التانية وقائمة الأسعار وحتة من المخزن. الـ Distroless: الصندوق فيه المنتج بس. حتى مطواة الفتح مش موجودة. لو حد سرق الصندوق في الطريق، ما هيلاقيش ولا أداة يستخدمها، وما هيعرفش حتى إيه تاني عندك في المخزن.
التعريف العلمي: Distroless هو base image بيحتوي على dependencies اللي runtime تطبيقك محتاجها فقط — ومفيهوش shell ولا package manager ولا أي userland binaries إضافية. المشروع طلع من Google تحت GoogleContainerTools سنة 2017، وبيتشحن على gcr.io/distroless. الصورة بتحتوي على glibc و SSL certificates و timezone data، وبس. مفيش /bin/sh ولا /usr/bin/apt ولا حتى ls.
المثال التنفيذي — من 1.13GB لـ 187MB
افتح Dockerfile لتطبيق Node.js بسيط. الطريقة الشائعة:
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY . .
CMD ["node", "server.js"]
الحجم النهائي بعد build: 1.13GB. عدد الـ CVEs على trivy image: 167 (منهم 24 High وواحد Critical).
الطريقة الـ Distroless بـ multi-stage build:
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY . .
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app /app
CMD ["server.js"]
الحجم: 187MB. الـ CVEs على trivy: 3 (صفر High، صفر Critical). نفس التطبيق بالظبط، نفس الأداء، نفس الـ runtime. الفرق الوحيد إن مفيش bash ولا apt ولا curl جوّا الصورة النهائية.
الأرقام بعد المقارنة على 4 تطبيقات
الأرقام دي من قياسات شخصية على 4 تطبيقات إنتاج، Trivy v0.50 مع database 2025-04:
- Node.js 20: 1.13GB → 187MB (−84%). CVEs: 167 → 3 (−98%).
- Python 3.12: 1.02GB → 142MB (−86%). CVEs: 142 → 4 (−97%).
- Go static binary: 312MB → 23MB (−93%) باستخدام
distroless/static. - Java 21 Spring Boot: 480MB → 218MB (−55%). الـ JVM نفسه ثقيل، المكسب الأقل هنا منطقي.
النتائج بتتفاوت على حسب طول الـ dependency tree بتاع تطبيقك. التطبيقات اللي بتعتمد على system libraries كتيرة هتاخد فايدة أقل.
ليه الـ CVEs بتنخفض بالشكل ده
كل CVE في الـ scan بيشاور على package فيه ثغرة معروفة. صورة node:20 الكاملة فيها bash و coreutils و apt-utils و libpcre و perl و dozens من المكتبات اللي تطبيقك ما بيلمسهاش أصلاً. كل package من دول لو فيه CVE، الـ scanner بيرفعه، حتى لو الكود اللي فيه الثغرة ما بيشتغلش في الـ runtime بتاعك.
Distroless بيشيل كل ده. الـ image ما فيها إلا node binary نفسه و libc و الـ SSL certs. مساحة الـ surface للهجوم بتنزل من آلاف الـ binaries لعدد أصابع الإيدين. ده مش "تقليل النظري" — ده تقليل فعلي للأكواد اللي ممكن تتنفّذ.
Trade-offs حقيقية محدش بيقولّك عليها
- مفيش shell للـ debugging.
docker exec -it container bashهيرجّعلك OCI runtime exec failed. الحل: استخدم variant الـ debug من Distroless نفسه (gcr.io/distroless/nodejs20-debian12:debug) فيه busybox، أو في Kubernetes 1.25+ استخدمkubectl debugمع ephemeral containers. - محتاج multi-stage build إلزامي. ما تقدرش تكتب
FROM distrolessوتنزّل dependencies جوّاها، لأن مفيش apt ولا npm جوّاها. لازم builder stage منفصلة. لو فريقك ما يعرفش الـ pattern ده، ده تكلفة تعلّم. - الـ healthcheck بـ curl ما يشتغلش. Distroless مفيهوش curl ولا wget. الحل: Kubernetes HTTP probe (
httpGet) من بره الكونتينر، أو إنك تنسخgrpc-health-probeكـ static binary من stage الـ builder. - Signal handling في Node.js child processes. Distroless مفيهوش
tiniولاdumb-init. لو تطبيقك بيعملspawnلـ subprocesses، ممكن تلاقي SIGTERM ما بيوصلش صح. الحل:docker run --initأو emptyDir mount مع tini static binary.
متى لا تستخدم Distroless
- صور Development و CI: محتاج apt و curl و الـ build tools. خلّيها
node:20عادي. - تطبيقات PHP أو Ruby تقليدية اللي بتعمل
shell_execأوsystem()في الـ runtime — هتكسر. - فرق صغيرة بدون Kubernetes ephemeral debug: التكلفة العملياتية لما حاجة تقع 3 الفجر أعلى من المكسب الأمني. ابدأ بـ slim أو alpine الأول.
- لو الـ CVE budget عندك مش مشغّل أصلاً: مش هتشوف الفرق. ابدأ بتفعيل scanning قبل ما تغيّر base.
الخطوة التالية
افتح Dockerfile واحد من تطبيقات الإنتاج عندك دلوقتي. شغّل: trivy image <your-image-tag> وعدّ الـ CVEs. لو الرقم فوق 20 وتطبيقك Node.js أو Python أو Go، حوّل الـ stage النهائية لـ gcr.io/distroless/<runtime>-debian12 مع multi-stage build في ساعة شغل. شغّل trivy تاني وقارن. لو نزل تحت 10، ده تحسّن أمني حقيقي مش cosmetic.
المصادر
- المشروع الرسمي: GoogleContainerTools/distroless على GitHub
- Google Cloud Blog 2017: إعلان مشروع Distroless
- Trivy CVE scanner — Aqua Security (مرجع scan الـ CVEs)
- Kubernetes Docs: Ephemeral Containers (v1.25+) للـ debugging
- Node.js Docker Best Practices — Snyk 2024
- قياسات شخصية على 4 تطبيقات إنتاج، يونيو 2025 (Trivy v0.50)