DOCKER::ROOT الإقليم VII · BuildKit · registry kernel live
REGION VII content-addressable cache = hash chain

البناء والتوزيع: من Dockerfile إلى طبقات Merkle

من أين تأتي الصورة أصلاً؟ كيف يصير نصّ Dockerfile كومةَ طبقات (III)؟ ولماذا ترتيب الأسطر يقرّر سرعة بنائك؟ الخيط الجامع: كل شيءٍ مُعنوَن بالمحتوى — وهذا يربط البناء والكاش والتوزيع في فكرةٍ واحدة.

النبذة

docker build يحوّل Dockerfile إلى صورة OCI. كل تعليمةٍ قد تنتج طبقة. الكاش يقرّر أيّها يُعاد بناؤه — بناءً على هاش المحتوى. والـ registry يخزّن ويوزّع بنفس العنونة، فيتشارك الطبقات عبر كل الصور والمستخدمين. البنّاء الحديث BuildKit أعاد تصميم هذا كـ رسمٍ موجّه لا دوري (DAG) بدل سلسلةٍ خطية.

اللغز المستفزّ — تنبّأ بالكاش، ثم اكسره

dockerfile
FROM python:3.12-slim COPY . /app RUN pip install -r /app/requirements.txt CMD ["python", "/app/main.py"]
  1. بنيتَه مرة. غيّرت سطراً في main.py فقط. تنبّأ قبل البناء: هل سيُعاد تشغيل pip install الثقيل؟ لماذا؟
  2. نفّذ وتحقّق. إن أُعيد pip install رغم أنك لم تلمس requirements.txt — اكتشفت «خطأ الـ Dockerfile» الأشهر. شخّص جذره: أي خطوةٍ في منطق الكاش جعلت تغيير main.py يُبطل طبقة الاعتماديات؟
  3. أعد ترتيب الأسطر بحيث لا يُعاد pip install إلا حين تتغيّر الاعتماديات فعلاً. أثبت بالقياس الزمني أن بناءك الثاني صار شبه فوري.
السؤال الذي يقودك

الكاش متسلسل — كل طبقةٍ تعتمد على هاش كل ما قبلها. إذا كان COPY . /app (الذي يشمل main.py) قبل RUN pip install، فأي تغييرٍ في أي ملف يُبدّل هاش طبقة COPY، فيُبطل كل ما بعدها. ما الترتيب الذي يفصل «ما يتغيّر نادراً» عن «ما يتغيّر كثيراً»؟

هذا اللغز فكريّ، وهو أكثر شيءٍ ستستعمله عملياً. لا حلّ مكتوب — استنتجه من نموذج «هاش متسلسل».

الدرس — البناء كاشتقاق من العنونة بالمحتوى

ليش كل تعليمةٍ قد تكون طبقة — النموذج القديم

البنّاء التقليدي حرفيّ: لكل تعليمة، يشغّل كونتينراً مؤقتاً من الطبقة السابقة، ينفّذها، ثم يلتقط الفرق كطبقةٍ جديدة. هذا يفسّر قواعد كتبتها يدوياً:

ليش الكاش يعمل هكذا: الهاش المتسلسل

ليش — النموذج الوحيد الذي تحتاجه

مفتاح الكاش لكل طبقة = دالة في (هاش الطبقة السابقة) + (نصّ التعليمة) + (للـ COPY/ADD: هاش محتوى الملفات). لأنه متسلسل، إبطال طبقةٍ يُبطل كل ما بعدها حتماً. خطأ اللغز جذره: وضع المحتوى متقلّب التغيّر (COPY .) مبكراً يسمّم السلسلة كلها.

النتيجة العملية: رتّب من الثابت إلى المتغيّر. انسخ requirements.txt وثبّت الاعتماديات قبل نسخ شيفرة التطبيق. فطبقة الاعتماديات الثقيلة تبقى مُكاشةً ما لم يتغيّر ملف الاعتماديات.

ليش BuildKit: من السلسلة الخطية إلى الـ DAG

البنّاء القديم خطّي صارم: لا توازي، كاش ساذج، لا أسرار آمنة. BuildKit يحلّل الـ Dockerfile إلى DAG من العمليات، ثم:

multi-stage: عدّة FROM، تنسخ من مرحلةٍ لأخرى بـ COPY --from=builder. تبني في صورةٍ ثقيلة ثم تنقل الناتج فقط لصورةٍ نحيلة (scratch/distroless) = سطح هجومٍ أقل (وصلٌ بـ IV) وحجمٌ أقل.

ليش العنونة بالمحتوى تُتقن التوزيع: الـ registry

الـ registry (تنفيذٌ لـ OCI Distribution Spec) يعيد استخدام فكرة III: كل شيءٍ مخاطَبٌ بالـ digest. البروتوكول (HTTP) من جزأين: blobs (الطبقات والـ config) وmanifests.

ليش — إزالة التكرار مجاناً

قبل رفع طبقة، يسأل العميل: HEAD /v2/<name>/blobs/sha256:<digest> — «أتملك هذه؟». إن «نعم» (لأن صورةً أخرى لأي مستخدمٍ تشاركها)، لا تُرفع. لهذا دفع صورةٍ على python:3.12 لا يرفع إلا طبقتك الصغيرة. content-addressing = إزالة تكرارٍ محلياً وعبر الشبكة وبين كل المستخدمين. الحصاد الأكبر لبذرة III.

تدفّق docker pull nginx: حلّ الاسم → GET manifests/latest (قد يكون index متعدد المعماريات) → اختر manifest معماريتك → اسحب الطبقات الغائبة فقط → اسحب config، فُكّ، جهّز overlay snapshot (III) ⇒ جاهز (V). multi-arch اشتقاقٌ من الـ index: docker buildx build --platform linux/amd64,linux/arm64.

اللغز — ابنِ صورةً بلا Dockerfile، وأثبت إزالة التكرار

  1. كاش جراحي: أصلح ترتيب الـ Dockerfile، وأثبت بـ time docker build الفرق بين تغييرٍ في الشيفرة (كاش يُصيب) وتغييرٍ في requirements.txt (كاش يُبطل من تلك النقطة). فسّر كل نتيجةٍ بنموذج الهاش المتسلسل.
  2. صورة بلا Dockerfile: بـ skopeo/أوامر OCI، جمّع صورةً يدوياً: ابدأ من طبقة alpine، أضف طبقتك، اكتب config و manifest بـ digests صحيحة، وادفعها إلى registry محلي (registry:2).
  3. أثبت إزالة التكرار: ادفع صورتين تشتركان في الأساس، وراقب أن الطبقات المشتركة خُزِّنت مرة واحدة، وأن الدفع الثاني يتخطّى الطبقات الموجودة بـ HEAD blobs/....
المصادر

OCI Distribution Spec (opencontainers/distribution-spec): أقسام «Pulling» و«Pushing». لـ BuildKit اقرأ moby/buildkit قسم «LLB» (التمثيل الوسيط كـ DAG). شغّل DOCKER_BUILDKIT=1 docker build --progress=plain لترى الـ DAG ينفّذ.

الخلاصة — الدورة اكتملت

أغلقت الحلقة: الصور التي شغّلناها (V) ووصلناها (VI) تأتي من بناءٍ وتوزيعٍ مبنيين كلياً على العنونة بالمحتوى من III. الكاش = هاشٌ متسلسل؛ رتّب من الثابت للمتغيّر. registry يخاطب بالـ digest فيزيل التكرار مجاناً. قواعد Dockerfile (&& للتنظيف، ترتيب الأسطر، multi-stage) صارت اشتقاقاتٍ من III و IV، لا وصفاتٍ محفوظة.

بذرة الغموض الأخيرة

لم يبقَ مفهومٌ جديد من الجذر. كل ما تبقّى هو جمع الخيوط في يقين: أن تنظر لأي إعدادٍ في Docker وتعرف فوراً أي بدائيةٍ يحرّك. هذا هو الإقليم الأخير.