مستوى القارئ: متوسط
Docker BuildKit Cache: قلل زمن الـ CI Build للنصف
هتخرج من المقال بإعداد عملي يقلل Docker build في CI من حوالي 6 دقائق إلى 2 أو 3 دقائق في مشاريع Node أو Python المتوسطة.
المشكلة باختصار
الطريقة الشائعة هي إنك تكتب Dockerfile شغال وخلاص. الطريقة دي بتفشل لمّا كل تعديل صغير في source file يخلي CI ينزل التبعيات من الأول. اللي بيحصل فعلاً إن ترتيب الطبقات والـ cache scope بيكسروا الاستفادة من BuildKit.
الافتراض إن عندك تطبيق ويب بحجم متوسط: 300 إلى 900 dependency، وCI runner مؤقت بيتبني من الصفر في كل Pull Request. في الحالة دي build مدته 6:20 دقيقة ممكن ينزل إلى 2:10 دقيقة بعد تفعيل cache mounts وregistry cache. الرقم تقديري، لكنه قريب من اللي بتشوفه في pipelines بتثبت npm أو pip كل مرة.
المفهوم: الكاش مش نوع واحد
ركز في الفرق ده. Docker layer cache بيشتغل لما التعليمة والملفات المرتبطة بها لم تتغير. لو كتبت COPY . . قبل RUN npm ci، أي تعديل في ملف JavaScript عادي ممكن يكسر طبقة تثبيت التبعيات. Docker نفسه يشرح إن ترتيب الـ layers وتقليل build context من أهم طرق تحسين الكاش.
BuildKit cache mounts مختلفة. هي مكان ثابت لتخزين cache مدير الحزم، مثل /root/.npm أو /root/.cache/pip. حتى لو طبقة RUN اتبنت من جديد، الحزم القديمة تفضل موجودة ويتنزّل الجديد فقط. Docker Docs تذكر إن cache mounts مفيدة تحديدًا لخطوات package managers لأنها تراكمية عبر builds.
إعداد عملي لـ Node.js
أفضل طريقة تبدأ بها هي إعادة ترتيب Dockerfile قبل إضافة أي أداة جديدة. بتكسب cache hit أعلى، وبتخسر بس شوية وضوح لو الفريق مش متعود على تقسيم COPY.
# syntax=docker/dockerfile:1.7
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY package.json ./
CMD ["node", "dist/server.js"]المهم هنا إن package-lock.json هو المفتاح. لو اتغير، طبيعي npm ci يتعاد. لو عدلت src/user.controller.ts فقط، المفروض التبعيات لا تتنزل من الصفر.
إعداد CI مع registry cache
لو runner مؤقت، local cache لوحده مش كفاية. استخدم registry cache عشان BuildKit يسحب cache من build سابق. Docker Docs توضح إن external cache شبه أساسي في CI/CD لأن بيئات البناء غالبًا قليلة أو معدومة الاستمرارية.
name: docker-build
on: [push, pull_request]
jobs:
image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: false
tags: ghcr.io/org/app:pr-${{ github.sha }}
cache-from: type=registry,ref=ghcr.io/org/app:buildcache
cache-to: type=registry,ref=ghcr.io/org/app:buildcache,mode=maxالـ trade-off هنا واضح. بتكسب build أسرع بين PRs، وبتخسر مساحة تخزين في registry واحتمال تعقيد بسيط في صلاحيات الدفع. لو عندك 20 PR يوميًا وكل build بيوفر 3 دقائق، فأنت وفرت حوالي ساعة runner يوميًا. لو الدقيقة بتكلف 0.008 دولار، التوفير مش ضخم ماليًا، لكنه مؤثر في سرعة مراجعة الكود.
قياس النتيجة بدل الإحساس
متقيسش بالانطباع. شغّل build مرتين. الأولى باردة، والثانية بعد تعديل ملف لا يغير dependencies. شوف زمن خطوة npm ci وزمن الـ build الكلي.
docker buildx build --progress=plain -t app:test .
# عدل ملف داخل src فقط
Measure-Command { docker buildx build --progress=plain -t app:test . }لو npm ci لسه بياخد نفس الزمن، غالبًا cache mount مش شغال أو ترتيب COPY غلط. لو الفرق ظهر محليًا ومظهرش في CI، راجع cache-from وcache-to وصلاحيات registry.
متى لا تستخدم هذه الطريقة
لا تستخدم registry cache لو المشروع صغير جدًا وbuild أقل من 45 ثانية. وقت سحب ورفع الكاش ممكن يبقى أكبر من المكسب. كمان لا تستخدم mode=max لو مساحة registry عندك محدودة جدًا أو عندك سياسة تنظيف aggressive. في monorepo كبير، ابدأ بخدمة واحدة بدل ما تطبق الكاش على كل الصور مرة واحدة.
المصادر
- Docker Docs: Optimize cache usage in builds
- Docker Docs: Cache storage backends
- Docker Docs: Multi-stage builds
الخطوة التالية
افتح Dockerfile في أكثر خدمة build عندك، وانقل تثبيت التبعيات قبل نسخ باقي الملفات. بعدها أضف cache mount، وقارن زمن build قبل وبعد في رقم واحد واضح.