تسريع Docker Build 10 مرات: BuildKit و Layer Caching بالتفاصيل
لو بتستنى 6 أو 8 دقائق كل مرة بتعمل فيها docker build لمشروع عادي، اللي قدامك هنا هينزّل الزمن ده لأقل من دقيقة، بدون ما تغيّر حاجة في الكود الفعلي. الشرح مبني على فرضية إنك شغّال على مشروع Node.js أو Go أو Python بحجم متوسط وبتعمل 10+ builds في اليوم.
المشكلة باختصار
الـ Docker build البطيء مش مجرد مضايقة شخصية. في CI/CD، كل pipeline بيعدّي هو انتظار للمطورين وتكلفة حقيقية على فاتورة الـ runners. فريق من 6 مطورين، كل واحد بيعمل 10 builds يوميًا، ببطء 5 دقائق زيادة، يساوي 25 ساعة مهدرة أسبوعيًا وحوالي 300 دولار في الشهر على GitHub Actions وحدها.
الطريقة السهلة اللي بيعملها أغلب الناس: FROM node:20، COPY . .، RUN npm install. الـ Dockerfile ده هيشتغل، لكن في كل build كل حاجة بتتعاد. الطريقة دي بتفشل لما المشروع يكبر، وده بالظبط السبب.
ليه Docker build بيبطأ أصلاً — مفهوم الطبقات
قبل ما ندخل في الحلول، لازم نفهم كيف Docker بيفكّر. خلينا نبدأ بمثال بسيط جدًا للمبتدئين.
تخيّل إنك بتحضّر ساندويتش كل يوم. كل يوم بتروح السوق، تشتري الرغيف، الجبنة، الطماطم، والصلصة من جديد، حتى لو كل اللي بتغيّره فعلًا هو الصلصة. ده هدر وقت واضح. الحل الذكي: تشتري الرغيف والجبنة والطماطم مرة واحدة (بيفضلوا صالحين لمدة)، وبس تروح تجيب الصلصة الجديدة كل مرة.
Docker بيعمل نفس الحاجة بالظبط. كل سطر RUN أو COPY أو ADD في الـ Dockerfile بينتج "طبقة" (layer). Docker بيعمل cache لكل طبقة بناءً على مدخلاتها. لو المدخلات لم تتغيّر، الطبقة بترجع من الـ cache بدون إعادة بناء. لو اتغيّرت، الطبقة دي وكل طبقة بعدها بتتعمل من الصفر.
التعريف التقني الدقيق
الـ cache key لكل layer هو hash مبني على: (1) نص التعليمة نفسها، (2) الـ parent layer السابق، (3) في COPY و ADD، hash محتوى الملفات المنسوخة. أي تغيير في أي واحد منهم بيعمل invalidate للـ cache لكل الطبقات التالية. المشكلة إن COPY . . بتعتبر أي تغيير في أي ملف في المشروع — حتى ملف README — تغيير في مدخلاتها، فبتفسخ الـ cache لكل حاجة تحتها بما فيها npm install.
الحل 1: ترتيب الـ Dockerfile — الأغلى أولاً
القاعدة الذهبية: ضع التعليمات النادرة التغيّر في الأعلى، والمتغيّرة كل commit في الأسفل. أغلى عملية في أغلب المشاريع هي تثبيت الـ dependencies، وهي تتغيّر نادرًا (بس لما تعدّل package.json أو go.mod).
مقارنة قبل وبعد:
# ❌ الطريقة الساذجة — كل تعديل كود بيعيد npm install
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/server.js"]