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

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

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

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

المنصة

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

الدعم

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

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

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

Docker BuildKit Cache: ليه الـ build بياخد 10 دقايق وإزاي تخلّيه دقيقة

📅 ٢٤ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
Docker BuildKit Cache: ليه الـ build بياخد 10 دقايق وإزاي تخلّيه دقيقة

Docker BuildKit Cache: ليه الـ build بياخد 10 دقايق وإزاي تخلّيه دقيقة

لو الـ docker build بتاعك بيعيد تحميل نفس الـ pip أو npm packages في كل مرة بتعدل فيها سطر كود، المشكلة مش في الشبكة ولا في حجم الـ image. المشكلة إنك مش مستخدم RUN --mount=type=cache. المقال ده فيه Dockerfile شغّال من إنتاج فعلي، قياس حقيقي على مشروع Python FastAPI (9 دقائق و 20 ثانية → دقيقة و 40 ثانية)، وتحذير من خطأ شائع بيخلي الـ cache يشتغل على جهازك ويفشل في GitHub Actions.

حاويات شحن ملونة مكدّسة تمثل طبقات Docker image والـ cache layers

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

الـ Dockerfile التقليدي بيعامل كل RUN كطبقة مستقلة. أي تعديل في سطر قبل COPY . . بيحرق الـ layer كله، ويعيد تنزيل كل الـ dependencies من الـ index العام. النتيجة: build الفعلي اللي تغير فيه 3 أسطر بياخد 9 دقائق، مع إن الحاجة اللي فعلاً اتعملت هي استدعاء pip install لمكتبة متغيرتش.

المفهوم بمثال بسيط: مخزن البقالة

تخيل إنك بتفتح سوبر ماركت كل يوم الصبح. الطريقة الغلط: بترمي كل البضاعة في آخر اليوم، وبتعيد شراءها من المورد الصبح التاني. الطريقة الصح: في مخزن ورا المحل بتسيب فيه البضاعة اللي لسه صالحة. BuildKit cache mount هو المخزن ده بالظبط: مجلد بيعيش بره الـ image layer، ومحتواه بيفضل موجود بين كل build والتاني، من غير ما يكبّر حجم الـ image النهائي.

الحل التقني: RUN mount=type=cache

BuildKit هو الـ builder الافتراضي في Docker 23.0 وما فوق. بيدعم تعليمة RUN --mount=type=cache اللي بتقول لـ Docker: "المجلد ده ميدخلش في الـ image النهائي، لكن احتفظ بيه كـ volume دائم بين الـ builds".

Dockerfile
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim

WORKDIR /app

RUN --mount=type=cache,target=/root/.cache/pip \
    --mount=type=bind,source=requirements.txt,target=requirements.txt \
    pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]

السطر المهم هو --mount=type=cache,target=/root/.cache/pip. المجلد ده هو اللي pip بيخزن فيه الـ wheels بعد تحميلها. أول build بيتأخر زي العادة. كل build بعد كده بيستخدم الـ cache المحلي، وبينزل بس الحزم اللي فعلاً اتغيرت.

القياس الفعلي على مشروع Python

الأرقام دي من مشروع FastAPI حقيقي بـ 47 dependency في requirements.txt، على runner فيه 4 vCPU و 8GB RAM:

  • قبل: 9 دقائق و 20 ثانية (في كل build، حتى لو التعديل سطر واحد).
  • بعد أول build مع cache mount: دقيقة و 40 ثانية.
  • بعد إضافة package جديد: دقيقة و 55 ثانية (بينزل الـ package الجديد فقط).

ده تحسّن أكتر من 80% لو بتعمل أكتر من build في اليوم. على فريق 5 مهندسين، بيفرق ساعة ونصف من وقت الانتظار يومياً.

لوحة تحليلات تعرض منحنى تحسّن زمن الـ build قبل وبعد تفعيل BuildKit cache mount

apt-get: حكاية أصعب شوية

مع apt، محتاج configuration إضافي: مجلدين cache، وحذف ملف docker-clean الموجود default في الـ base images.

Dockerfile
# syntax=docker/dockerfile:1.7
FROM ubuntu:24.04

RUN rm -f /etc/apt/apt.conf.d/docker-clean

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && \
    apt-get install -y --no-install-recommends \
        curl ca-certificates git

ملاحظات لازم تركز فيها:

  • sharing=locked إجباري مع apt. الـ tool بياخد lock على الملفات، ومن غير الخاصية دي لو عندك build بيشتغلوا بالتوازي بيحصل corruption للـ cache.
  • حذف /etc/apt/apt.conf.d/docker-clean ضروري. الملف ده موجود default في base images زي ubuntu و debian، وبيمسح الـ cache تلقائي بعد كل apt install، وبيلغي فايدة الـ mount.
  • بدل ما تضيف rm -rf /var/lib/apt/lists/* زي ما كلنا بنعمل بالعادة، متضيفهوش لما تكون مستخدم cache mount، لأن الـ cache نفسه مش داخل الـ layer أصلاً.

الـ trap في GitHub Actions

الـ trade-off اللي مش كل الناس واعيه بيه: cache-from: type=gha و cache-to: type=gha في docker/build-push-action بتحفظ الـ image layers بس، مش بتحفظ محتوى الـ RUN --mount=type=cache. نتيجة: الـ build على الـ local بيبقى دقيقة، وعلى CI بيفضل 9 دقائق، ومش فاهم ليه.

الحل: استخدم action اسمه buildkit-cache-dance اللي بيـ export و import محتوى الـ cache mounts يدوياً قبل وبعد الـ build:

YAML
- name: Inject pip cache into buildx
  uses: reproducible-containers/buildkit-cache-dance@v3
  with:
    cache-map: |
      {
        "pip-cache": "/root/.cache/pip"
      }
    skip-extraction: ${{ steps.build.outputs.cache-hit }}

الـ trade-off هنا: الـ step ده بيضيف 15–30 ثانية لكل build (export/import للـ cache). في 90% من الحالات الوفر أكبر بكتير من التكلفة دي، لكن لو الـ cache حجمه أقل من 50MB، overhead مش مستحق.

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

cache mount بيضيف تعقيد بدون فايدة واضحة في الحالات دي:

  • لو بتعمل build مرة واحدة في الأسبوع، الـ layer caching العادي كفاية.
  • لو الـ dependencies صغيرة جداً (أقل من 20MB)، الفرق في الوقت هيكون ثواني.
  • لو بتستخدم runners مؤقتة بتتعمل وتتمسح كل build (scale-to-zero)، الـ cache mount بيتمسح مع الـ runner نفسه. هتحتاج remote cache backend (S3 مثلاً).
  • لو عندك npm ci في مشروع Node صغير، غالباً package-lock.json بيـ cache الـ layer بشكل كافي لو الـ COPY package*.json قبل الـ COPY . ..

الافتراضات اللي الشرح مبني عليها

الأرقام اللي فوق مبنية على: Docker 24.0+، مشروع Python بـ 47 dependency، و runner بـ 4 vCPU. لو عندك حجم مختلف (مثلاً 200 dependency أو runner بـ 1 vCPU)، النسب هتبقى قريبة لكن الأرقام المطلقة هتختلف.

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

افتح الـ Dockerfile الحالي عندك، ضيف # syntax=docker/dockerfile:1.7 في أول سطر، وحوّل أكبر RUN pip install أو RUN apt-get install لصيغة cache mount. قيس الـ build قبل وبعد بـ time docker build . مرتين متتاليتين. لو الفرق بين المرتين أقل من 40%، غالباً فيه sharing=locked ناقص، أو layer invalidation بيحصل قبل الـ RUN. لو حصل ده، ابعت مخرجات docker build --progress=plain وهنفهم السبب.

المصادر

  • Docker Docs — Optimize cache usage in builds
  • Docker Docs — GitHub Actions cache backend
  • moby/buildkit Issue #3011 — cache-from gha doesn't persist mount caches
  • Depot — How to use cache mounts to speed up Docker builds
  • reproducible-containers/buildkit-cache-dance

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

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

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