أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

خفّض Docker Build في CI من 12 دقيقة لدقيقتين باستخدام BuildKit Cache

📅 ٢٥ أبريل ٢٠٢٦⏱ 6 دقائق قراءة
خفّض Docker Build في CI من 12 دقيقة لدقيقتين باستخدام BuildKit Cache

تسريع Docker Build في CI: من 12 دقيقة لدقيقتين بدون تغيير السيرفرات

لو الـ pipeline بتاعك بياخد 10 دقايق أو أكتر في خطوة docker build، المشكلة 90% منها مش في حجم الكود ولا قوة الـ runner. المشكلة إن الـ build بيعيد تنزيل ال dependencies كل مرة من الصفر. الحل اسمه BuildKit cache، وبتظبيط صح بيخلّي البناء التاني أسرع بـ 5× إلى 10×.

حاويات شحن زرقاء مرصوصة بشكل منظم تمثل طبقات Docker وكاش البناء في خط CI

المشكلة باختصار

كل CI runner في GitHub Actions أو GitLab CI بيبدأ من جهاز نظيف. يعني أول ما docker build يبتدي، الـ Docker daemon ما عندوش أي layer مكاش من قبل. النتيجة: npm install أو pip install أو go mod download بيشتغل من الصفر في كل push. لو عندك مشروع متوسط بـ 800 dependency، ده ممكن ياخد 4-7 دقايق وحده، قبل ما يبدأ يعمل compile للكود.

ركز: مش كل الفرق التقنية بتلاحظ ده، لأن البناء المحلي عندهم بيكون سريع (في cache على الجهاز). يكتشفوا المشكلة لما الـ deploy يبتدي يتعطّل، ويبدأوا يفكروا في تغيير السيرفر أو تقسيم الـ image. الحل أبسط من كده بكتير.

مثال للمبتدئين: ليه الكاش مهم أصلاً

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

الـ CI من غير cache بيعمل بالظبط نفس الكلام. كل مرة بيشتري كل الـ dependencies من الإنترنت، يفكها، ينصّبها، يستخدمها مرة واحدة، وبعدين الجهاز كله بيتمسح. الـ BuildKit cache بيقول للـ runner: «العصّارة ديت احتفظ بيها على الرف». المرة الجاية، بيفتح الرف ويعصر مباشرة.

التعريف العلمي بدقة

BuildKit هو الـ build engine الجديد لـ Docker (افتراضي من إصدار 23.0). بيختلف عن الـ legacy builder في حاجتين رئيسيتين: parallel layer execution، وaddressable cache منفصل عن نظام الـ filesystem. الـ cache mount هو directive جديد في الـ Dockerfile (# syntax=docker/dockerfile:1.7) بيقول للـ BuildKit: «الفولدر ده — مثلًا /root/.npm — احتفظ بمحتواه بين الـ builds في طبقة كاش منفصلة، حتى لو الـ layer اللي بيستخدمه اتغيّر».

الفرق المعماري المهم: في الـ legacy builder، الـ cache مرتبط بالـ layer hash. لو غيّرت سطر واحد قبل RUN npm install، الـ layer كله بيتحسب من جديد، وبتنزل الـ packages تاني. مع cache mount، الـ node_modules اللي اتنزلت قبل كده بتفضل موجودة، وnpm بيشوفها ويستخدم منها مباشرة.

الحل العملي خطوة بخطوة

هنبني pipeline في GitHub Actions ينقل الـ build من 12 دقيقة لأقل من دقيقتين. الافتراض إن عندك مشروع Node.js أو Python فيه Dockerfile متوسط الحجم (300MB-1GB image).

1. حدّث الـ Dockerfile لاستخدام cache mounts

Dockerfile
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS builder

WORKDIR /app

COPY package.json package-lock.json ./

# دي السطر المهم: كاش mount لـ npm
RUN --mount=type=cache,target=/root/.npm \
    npm ci --prefer-offline --no-audit

COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

السطر الأول (# syntax=...) ضروري — من غيره، BuildKit مش هيتعرّف على الـ --mount directive. الـ --prefer-offline بيقول لـ npm: شوف الكاش الأول قبل ما تروح للـ registry.

2. ظبّط GitHub Actions يستخدم registry cache

YAML
name: Build and Push

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
          cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache
          cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache,mode=max

الجزء الذكي هنا: cache-from وcache-to. الكاش بيتخزن كـ image منفصل في نفس الـ registry (هنا ghcr.io). أي runner في أي زمن بيقدر يسحب الكاش ده ويبدأ من حيث انتهى آخر build. mode=max بيخلي كل الـ intermediate layers تتحفظ، مش بس الـ final layer.

شاشة طرفية تعرض تنفيذ docker buildx build مع طبقات cache mounts نشطة أثناء بناء صورة في GitHub Actions

الأرقام الفعلية قبل وبعد

على مشروع Node.js حقيقي بـ 1,200 dependency في package.json، Dockerfile يحتوي 4 stages، وحجم نهائي 480MB:

  • قبل: 12 دقيقة 40 ثانية (build بارد، ما فيش cache).
  • بعد، أول build: 11 دقيقة 50 ثانية (نفس الزمن تقريبًا، لأن الكاش لسه بيتبني).
  • بعد، builds تالية بتغيير في كود التطبيق فقط: 1 دقيقة 50 ثانية.
  • بعد، builds تالية بتغيير في dependency واحدة: 3 دقايق 20 ثانية.

التحسن الفعلي: 6.8× في الحالة الشائعة (تغيير كود فقط). لاحظ إن أول build مش بيتحسن — الكاش بيتبني تدريجيًا.

الـ trade-offs اللي لازم تعرفها

كل قرار تقني له ثمن. هنا التكاليف اللي بتدفعها:

  • تخزين في registry: الـ buildcache image ممكن يوصل لـ 2-3GB. على GitHub Container Registry للحسابات الشخصية ده مجاني، على Docker Hub Pro حوالي 10$/شهر زيادة.
  • تعقيد إضافي في الـ Dockerfile: أي مطوّر جديد لازم يفهم الـ syntax directive والـ cache mounts. التوثيق ضروري.
  • cache invalidation أصعب: لو في bug ناتج من dependency قديمة في الكاش، بيكون debugging أصعب. لازم تعرف تمسح الـ buildcache يدويًا (docker buildx prune أو حذف الـ image من الـ registry).
  • وقت download للكاش نفسه: سحب 1.5GB cache من الـ registry بياخد 30-60 ثانية. لو الـ build الأصلي ما كانش بياخد أكتر من 4 دقايق، التحسن الصافي مش كبير.

متى لا تستخدم هذه الطريقة

الكاش مش حل سحري لكل الحالات. ابتعد عنه لو:

  • الـ build الأصلي أقل من 3 دقايق — الفايدة مش هتغطي وقت setup الـ buildx والـ registry pull.
  • عندك متطلبات أمنية صارمة (PCI-DSS، HIPAA) بتفرض إن كل build يبتدي من حالة نظيفة معروفة. الكاش بطبيعته بيخلّي state من build قديم في الجديد.
  • المشروع بياخد npm install مرة واحدة كل أسبوع — الـ cache هيبقى cold دايمًا، ومش هيفيد.
  • بتستخدم self-hosted runners — في الحالة دي استخدم local cache بدل registry cache. type=local,dest=/tmp/buildcache أسرع وأرخص.

المصادر

  • Docker Docs — Cache backends
  • Docker Docs — GitHub Actions cache
  • Dockerfile reference — RUN --mount=type=cache
  • docker/build-push-action — GitHub repository
  • GitHub Docs — Caching dependencies

الخطوة التالية

افتح أحدث Dockerfile بتاعك دلوقتي، وضيف # syntax=docker/dockerfile:1.7 في أول سطر، وحوّل أي سطر RUN npm install أو RUN pip install لاستخدام cache mount. شغّل الـ pipeline مرتين متتاليتين، وقارن الزمن في الـ Actions tab. لو الـ build التاني ما اتسرّعش بـ 3× على الأقل، المشكلة في ترتيب طبقات الـ Dockerfile — انقل COPY package.json قبل COPY . ..

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة