BuildKit cache mounts: الدليل العملي لتسريع Docker build
لو الـ docker build بتاعك في CI بياخد 6–10 دقايق وأغلب الوقت في npm install أو pip install أو apt-get install، المشكلة مش في السيرفر ومش محتاج تكبّره. BuildKit cache mounts بتخلّي نفس الخطوة تخلص في 30–60 ثانية بدون ما تغيّر سطر واحد في الكود.
المشكلة باختصار
كل build في CI بيبدأ من زيرو تقريبًا. الـ runner جديد، الـ disk فاضي، وأي layer فيها RUN npm ci بتحمّل كل الـ packages من الإنترنت كل مرة. حتى لو عدّلت سطر في كود الـ app بس، الـ package manager بيعيد الشغل كله لو الـ package.json اتلمس، لأن الـ layer cache بيشتغل all-or-nothing.
النتيجة العملية: فريق من 10 مطورين بيدفع 10 push/يوم، وكل push بيستنى 8 دقايق في الـ pipeline. ده 80 دقيقة compute يومي مهدرة في تحميل نفس الـ node_modules مرة بعد مرة. وبتنعكس على تكلفة الـ CI وعلى سرعة feedback loop للمطورين.
ليه الـ layer cache الطبقي بيفشل في CI
الـ Docker layer cache الكلاسيكي بيعتمد على قاعدة: "لو inputs الـ step ما اتغيّرتش، استخدم الطبقة اللي قبل." مشكلته الأساسية إنه all-or-nothing. مثلاً، لو package.json فيه dependency جديدة واحدة، الـ layer كلها بتتعاد، وnpm ci بيحمّل الـ 500 package من أول وجديد.
كمان في CI، الـ runner غالبًا ephemeral (بيتعمل ويتمسح)، فأي cache على الـ disk بيروح بعد كل build إلا لو استخدمت حل صريح زي GitHub Actions cache أو registry-based cache.
Cache mounts: الفكرة بمثال بسيط
تخيّل إنك كل صباح بتعمل قهوة. الطريقة الأولى: تروح السوبرماركت وتشتري علبة بن جديدة كل يوم. الطريقة دي "layer cache"، لو السوبرماركت قفل أو السعر زاد، طابور ده معناه بطء. الطريقة التانية: عندك علبة بن ثابتة في المطبخ، وبتشتري بس اللي خلص. دي cache mount: مخزن ثابت بين الـ builds، مش بيتحط في الـ image النهائية، وبيستفيد بس من التغيير.
الصياغة العلمية: الـ cache mount هو directory بـ BuildKit بيعمله mount على step محدد وقت الـ build، مخزّن في BuildKit cache backend (مش طبقة في الـ image). الـ directory ده persistent عبر الـ builds المتتالية، وأي package manager بيستخدم نفس المسار بيلاقي الملفات اللي نزّلها قبل كده. النتيجة: pip install بيحمّل بس الـ wheels الجديدة بدل الـ 50 package كلهم.
أمثلة تنفيذية جاهزة
أول حاجة، تأكد إن BuildKit شغّال. في Docker 23+ ده default. لو أقدم، فعّله بالأمر التالي أو بسطر syntax في أول الـ Dockerfile: