{"id":"l-dk-2-4","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"افحص images ‏notes-api بحثًا عن الثغرات المعروفة باستخدام Docker Scout، وصنّف ما تجده حسب الأولوية، واستخدم secret mounts في BuildKit حتى لا تصل الـ tokens المطلوبة وقت البناء إلى أي layer في الـ image.","html":"<p>أسوأ حادث في سجلّي لم يكن انهيارًا. خطوة بناء احتاجت إلى token لـ registry خاص بـ npm، فمرّره أحدهم بوصفه وسيط بناء (build argument). ذهبت الـ image إلى registry متاح للقراءة على نطاق واسع، وبعد أشهر وجد تدقيق روتيني الـ token قابعًا في البيانات الوصفية للـ image، صالحًا طوال تلك المدة. هذا الدرس عمّا بداخل الـ images: الأجزاء التي لم تكتبها، والأجزاء التي لم تقصد تضمينها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">افحص قبل أن تشحن</h2>\n<p>كل حزمة في الـ image، من <code dir=\"ltr\">libssl</code> في Debian إلى <code dir=\"ltr\">express</code> في npm، لها تاريخ علني من الثغرات (CVEs). الماسح يقرأ جرد الحزم في الـ image ويطابقه مع قواعد البيانات تلك. ‏Docker Scout مدمج في واجهة Docker CLI:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> quickview</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> cves</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --only-severity</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> critical,high</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> recommendations</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">quickview</code> يعطي ملخّصًا في شاشة واحدة: عدد الثغرات في الـ image لديك، وفي الـ base image التي بُنيت عليها، وفي إصدار أحدث من تلك الـ base إن وُجد. والأمر <code dir=\"ltr\">cves</code> يسرد كل نتيجة مع الحزمة، والإصدار المثبّت، والإصدار الذي يصلحها. والأمر <code dir=\"ltr\">recommendations</code> يقترح tags لـ base images فيها ثغرات معروفة أقل. ‏Trivy وGrype ماسحان مفتوحا المصدر شائعان يؤدّيان العمل نفسه؛ اختر واحدًا واستخدمه في كل مكان.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">صنّف بترتيب معقول</h2>\n<p>القائمة الطويلة من النتائج ليست قائمة مهام. اعمل عليها هكذا:</p>\n<ol>\n<li><strong>نتائج الـ base image أولًا.</strong> إذا جاءت معظم النتائج من حزم نظام التشغيل، فحدّث إلى base مرقّعة حديثًا أو انتقل إلى base أصغر. الانتقال إلى distroless في الدرس 2.2 يزيل معظمها بخطوة واحدة.</li>\n<li><strong>اعتماديات التطبيق القابلة للإصلاح بعد ذلك.</strong> إذا وُجد إصدار مُصلَح، فارفع رقم إصداره في <code dir=\"ltr\">package.json</code>، وشغّل <code dir=\"ltr\">npm ci</code>، وأعِد البناء.</li>\n<li><strong>النتائج غير القابلة للإصلاح تحتاج إلى قرار، لا إلى صمت.</strong> اكتب لماذا لا يمكن الوصول إلى مسار الكود ذاك، أو ما الذي يخفّف الخطر، وحدّد موعدًا للنظر فيها مجددًا.</li>\n</ol>\n<p>ثم اجعل الفحص بوّابة حتى لا تتسلّل الانتكاسات (regressions):</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> cves</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --only-severity</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> critical,high</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --exit-code</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span></span></code></pre></div>\n<p>مع <code dir=\"ltr\">--exit-code</code> يفشل الأمر حين يجد ثغرات بتلك الدرجات من الخطورة، فتفشل مهمّة الـ CI قبل الدفع. وما دمت في الـ CI، فاطلب من أداة البناء بيانات سلسلة التوريد أيضًا: ‏<code dir=\"ltr\">docker buildx build --sbom=true --provenance=mode=max …</code> يرفق بالـ image قائمة مكوّنات البرمجيات (SBOM) وبيانات مصدر البناء (provenance)، فيصبح التدقيق التالي بحثًا سريعًا بدلًا من تحقيق.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">لماذا يهمّ هذا</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>لماذا تفحص دوريًا، لا عند البناء فقط</span></p>\n<p>تُنشر كل يوم ثغرات CVE جديدة في حزم لم تتغيّر. الـ image التي اجتازت الفحص نظيفة عند الإطلاق قد تظهر فيها نتيجة حرجة الشهر القادم. أعِد فحص ما هو منشور، أسبوعيًا على الأقل، وأعِد البناء على base جديدة حين يلزم.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">كيف تتسرّب الأسرار إلى الـ images</h2>\n<p>الـ images تتذكّر أكثر مما تظن. ثلاثة تسريبات شائعة:</p>\n<ul>\n<li><strong><code dir=\"ltr\">ENV API_KEY=…</code></strong> تُخزَّن في إعدادات الـ image وتُضبط في كل container. والأمر <code dir=\"ltr\">docker image inspect</code> يُظهرها.</li>\n<li><strong><code dir=\"ltr\">ARG NPM_TOKEN</code></strong> المستخدمة في خطوة <code dir=\"ltr\">RUN</code> تُسجَّل في سجلّ بناء الـ image. والأمر <code dir=\"ltr\">docker history --no-trunc</code> قد يكشفها.</li>\n<li><strong>ملف كُتب ثم حُذف لاحقًا</strong> (<code dir=\"ltr\">COPY .npmrc</code> ثم <code dir=\"ltr\">RUN rm .npmrc</code>) ما زال موجودًا في الـ layer السابقة. أي شخص لديه الـ image يستطيع استخراج تلك الـ layer.</li>\n</ul>\n<p>تحذّر build checks في Docker من الأوّلين (<code dir=\"ltr\">SecretsUsedInArgOrEnv</code>) حين يوحي اسم المتغيّر بأنه سرّ. خذ التحذير بجدية.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ secret mounts في BuildKit</h2>\n<p>الأداة الصحيحة هي <strong>الـ secret mount</strong>: يُتاح السرّ لخطوة <code dir=\"ltr\">RUN</code> واحدة على هيئة ملف (أو متغيّر بيئة) ولا يُكتب أبدًا في layer. بناء notes-api يحتاج إلى token لحزمة خاصة، لذا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> node:24-slim </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> build</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WORKDIR</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /app</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">COPY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> package.json package-lock.json ./</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> --mount=type=secret,id=npmrc,target=/root/.npmrc \\</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    --mount=type=cache,target=/root/.npm \\</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    npm ci</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> build</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> id=npmrc,src=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">$HOME</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/.npmrc</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> .</span></span></code></pre></div>\n<p>خلال خطوة <code dir=\"ltr\">RUN</code> تلك، يجد npm إعداداته في <code dir=\"ltr\">/root/.npmrc</code>. وقبلها وبعدها لا يوجد الملف، ولا يُخزَّن عنه شيء في الـ image أو في الـ build cache. ودون <code dir=\"ltr\">target</code>، تُركَّب الأسرار في <code dir=\"ltr\">/run/secrets/&lt;id&gt;</code>. وإذا كانت أداة ما تقرأ الـ token من متغيّر بيئة، فركّبه متغيّرَ بيئة بـ <code dir=\"ltr\">RUN --mount=type=secret,id=npm_token,env=NPM_TOKEN npm ci</code> ومرّر <code dir=\"ltr\">--secret id=npm_token,env=NPM_TOKEN</code> في سطر الأوامر، فتُقرأ القيمة من المتغيّر <code dir=\"ltr\">NPM_TOKEN</code> في الـ shell لديك.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>«إنها مرحلة البناء فقط، ولن تُشحن»</span></p>\n<p>الـ multi-stage builds تُبقي مرحلة البناء خارج الـ image النهائية فعلًا. لكن مراحل البناء تُدفع هي الأخرى: في صورة cache مُصدَّر مع <code dir=\"ltr\">mode=max</code>، وimages اختبار من <code dir=\"ltr\">--target build</code>، وtags للتشخيص. الـ token في أي layer من أي مرحلة هو token في الـ registry لديك. استخدم الـ secret mounts في كل مرحلة.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">أسرار وقت التشغيل ليست من شأن الـ image</h2>\n<p>كلمة مرور قاعدة البيانات التي يستخدمها notes-api في الإنتاج لا مكان لها في الـ image إطلاقًا. إنها تصل حين يبدأ الـ container: من متغيّر بيئة في Compose، أو من Kubernetes Secret، أو من مدير الأسرار في منصّتك السحابية. ستربط الأوّلين في الأقسام القادمة. تبقى الـ image متطابقة عبر البيئات؛ ولا يتغيّر إلا الإعداد المحقون.</p>\n<p>انتهى القسم الثاني: الـ image لديك نحيفة، وبلا صلاحيات، وقابلة للتتبّع، ونظيفة. في القسم التالي ستعطيها قاعدة بيانات تتحدّث معها، باستخدام Docker Compose.</p>\n","toc":[{"id":"sec-1","text":"افحص قبل أن تشحن","level":2},{"id":"sec-2","text":"صنّف بترتيب معقول","level":2},{"id":"sec-3","text":"كيف تتسرّب الأسرار إلى الـ images","level":2},{"id":"sec-4","text":"الـ secret mounts في BuildKit","level":2},{"id":"sec-5","text":"أسرار وقت التشغيل ليست من شأن الـ image","level":2}],"takeaways":["الأمر <code dir=\"ltr\">docker scout cves</code> يسرد ثغرات CVE المعروفة لكل حزمة؛ ومع <code dir=\"ltr\">--only-severity critical,high --exit-code</code> يتحوّل إلى بوّابة في الـ CI.","أصلح نتائج الـ base image أولًا بتحديثها أو تغييرها؛ والأمر <code dir=\"ltr\">docker scout recommendations</code> يقترح بدائل.","كل ما يُمرَّر بـ <code dir=\"ltr\">ARG</code> أو <code dir=\"ltr\">ENV</code>، أو يُكتب في ملف داخل خطوة <code dir=\"ltr\">RUN</code>، يمكن استرجاعه من الـ image، حتى لو حذفته خطوة لاحقة.","التعليمة <code dir=\"ltr\">RUN --mount=type=secret</code> تتيح السرّ لخطوة بناء واحدة دون كتابته في أي layer.","أسرار وقت التشغيل مثل كلمات مرور قاعدة البيانات تُحقن عند بدء الـ container، ولا تُبنى داخل الـ image أبدًا."],"further":[{"title":"Docker Scout quickstart","url":"https://docs.docker.com/scout/quickstart/"},{"title":"Build secrets","url":"https://docs.docker.com/build/building/secrets/"},{"title":"docker scout cves","url":"https://docs.docker.com/reference/cli/docker/scout/cves/"}],"quiz":[{"q":"<p>فحص <code dir=\"ltr\">notes-api:1.4.0</code> يُبلغ عن 30 ثغرة CVE، منها 27 في حزم Debian القادمة من الـ base image. ما الخطوة الأولى الأكثر فاعلية؟</p>","options":[{"text":"أخفِ نتائج الـ base image، لأنك لم تكتب ذلك الكود.","why":"أنت تشحن ذلك الكود، فأنت تتحمّل مخاطره. الإخفاء دون تحليل يخبّئ تعرّضًا حقيقيًا."},{"text":"أعِد البناء على base image محدّثة أو أصغر، ثم أعِد الفحص.","why":"صحيح. معظم نتائج الـ base تختفي مع base مرقّعة أو أنحف مثل distroless. تغيير واحد يصلح عشرات النتائج."},{"text":"رقّع كل حزمة Debian بـ <code dir=\"ltr\">apt-get upgrade</code> في مرحلة التشغيل.","why":"هذا يجعل البناء غير متوقّع، ويتركك تصون نظام التشغيل يدويًا. أصلح الـ base image نفسها."},{"text":"تجاهلها حتى يسأل عميل.","why":"ثغرات CVE المعروفة القابلة للإصلاح في الـ images المشحونة هي بالضبط ما يبحث عنه المهاجمون."}],"answer":1},{"q":"<p>خطوة البناء هذه تحتاج إلى token لـ npm. ما الخطأ فيها؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ARG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> NPM_TOKEN</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> echo </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"//npm.pkg.github.com/:_authToken=${NPM_TOKEN}\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> > .npmrc \\</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x26;&#x26; npm ci &#x26;&#x26; rm .npmrc</span></span></code></pre></div>\n","options":[{"text":"لا شيء؛ الملف <code dir=\"ltr\">.npmrc</code> يُحذف في الخطوة نفسها، فلا يبقى الـ token.","why":"الملف ذهب، لكن وسائط البناء (build arguments) تُسجَّل في سجلّ بناء الـ image، فيبقى الـ token قابلًا للاسترجاع."},{"text":"قيم <code dir=\"ltr\">ARG</code> لا يمكن استخدامها داخل <code dir=\"ltr\">RUN</code>.","why":"يمكن ذلك؛ وهذا ما يجعل هذا النمط مغريًا."},{"text":"يجب تمرير الـ token بـ <code dir=\"ltr\">ENV</code> بدلًا من <code dir=\"ltr\">ARG</code>.","why":"التعليمة <code dir=\"ltr\">ENV</code> أسوأ: فهي تُخزَّن في إعدادات الـ image وتُضبط في كل container يعمل."},{"text":"الـ token يُسجَّل مع وسائط البناء ويمكن قراءته من سجلّ الـ image؛ استخدم secret mount بدلًا من ذلك.","why":"صحيح. توثيق Docker نفسه يحذّر من أن <code dir=\"ltr\">ARG</code> و<code dir=\"ltr\">ENV</code> ليستا للأسرار. أما الـ secret mount فلا يوجد إلا خلال تلك الخطوة الواحدة."}],"answer":3},{"q":"<p>ماذا يعطي هذا السطر لخطوة <code dir=\"ltr\">npm ci</code>؟ <code dir=\"ltr\">RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci</code></p>","options":[{"text":"ملفًا مؤقتًا في <code dir=\"ltr\">/root/.npmrc</code> يحتوي على السرّ، مرئيًا خلال هذه الخطوة فقط وغائبًا عن الـ layer.","why":"صحيح. يظهر الـ mount للأمر ويختفي بعده؛ ولا يُخزَّن شيء من محتواه في الـ image."},{"text":"متغيّر بيئة اسمه <code dir=\"ltr\">npmrc</code> مضبوطًا لكل الخطوات اللاحقة.","why":"هذه الصيغة تركّب ملفًا في <code dir=\"ltr\">target</code>. والـ secret mounts لا تنتقل أبدًا إلى الخطوات اللاحقة."},{"text":"نسخة من <code dir=\"ltr\">.npmrc</code> مدمجة داخل الـ image تحت <code dir=\"ltr\">/root</code>.","why":"هذا ما تفعله <code dir=\"ltr\">COPY</code>. أما الـ secret mount فلا يُكتب أبدًا في layer."},{"text":"لا شيء، إلا إذا كانت الـ image تعمل أيضًا بصلاحيات root.","why":"الـ mount يعمل لأي مستخدم ينفّذ الخطوة؛ والمسار الهدف هنا يطابق فحسب المجلد الرئيسي (home) للمستخدم root."}],"answer":0},{"q":"<p>من أين يجب أن تأتي كلمة مرور قاعدة بيانات الإنتاج لـ notes-api؟</p>","options":[{"text":"من ملف <code dir=\"ltr\">.env</code> يُنسخ إلى الـ image أثناء البناء.","why":"هذا يدمج كلمة المرور في كل نسخة من الـ image، في كل registry وcache تصل إليه."},{"text":"من وسيط بناء، حتى تحصل image كل بيئة على كلمة مرورها الخاصة.","why":"وسائط البناء تُسجَّل في سجلّ الـ image، والـ images الخاصة بكل بيئة تكسر مبدأ «اختبر ما تشحنه»."},{"text":"تُحقن وقت التشغيل، مثلًا من Kubernetes Secret أو من Compose secret، ولا تُخزَّن في الـ image أبدًا.","why":"صحيح. الـ image نفسها تعمل في كل مكان؛ وكل بيئة تقدّم بيانات اعتمادها حين يبدأ الـ container."},{"text":"مكتوبة مباشرة في <code dir=\"ltr\">dist/config.js</code> ومحميّة بالتشغيل بمستخدم غير root.","why":"المستخدم غير الـ root ما زال يستطيع قراءة كوده، وكذلك أي شخص يسحب الـ image."}],"answer":2}],"exercise":{"kind":"spot-bug","language":"dockerfile","code":"# syntax=docker/dockerfile:1\nFROM node:24-slim\nARG NPM_TOKEN\nWORKDIR /app\nCOPY package.json package-lock.json ./\nRUN echo \"//npm.pkg.github.com/:_authToken=${NPM_TOKEN}\" > .npmrc\nRUN npm ci --omit=dev\nRUN rm .npmrc\nCOPY dist ./dist\nUSER node\nCMD [\"node\", \"dist/server.js\"]","bugLines":[3,6],"title":"أين يتسرّب الـ token","prompt":"<p>هذه الـ image لأداة داخلية تثبّت حزمًا من registry خاص. يعتقد كاتبها أن حذف <code dir=\"ltr\">.npmrc</code> في السطر 8 يُبقي الـ token آمنًا. اختر <strong>السطرين</strong> اللذين يضعان الـ token داخل الـ image حيث يستطيع أي شخص يسحبها استرجاعه.</p>\n","hints":["كيف يدخل الـ token إلى البناء، وهل تُسجَّل تلك الآلية في أي مكان؟","الملف المكتوب في layer ما يبقى موجودًا في تلك الـ layer حتى لو حذفته layer لاحقة."],"explanation":"<p>السطر 3 يمرّر الـ token بوصفه وسيط بناء (build argument)، وهذا يُسجَّل في سجلّ بناء الـ image. والسطر 6 يكتبه في <code dir=\"ltr\">.npmrc</code> داخل layer خاصة به؛ أما <code dir=\"ltr\">rm</code> في السطر 8 فيخفي الملف في layer لاحقة فقط، فاستخراج layer السطر 6 يكشفه. استخدم secret mount، فهو لا يوجد إلا للخطوة الوحيدة التي تحتاج إليه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> --mount=type=secret,id=npmrc,target=/root/.npmrc \\</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    npm ci --omit=dev</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> build</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> id=npmrc,src=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">$HOME</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/.npmrc</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> tool:1.0.0</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> .</span></span></code></pre></div>\n<p>ثم دوّر الـ token، لأن كل نسخة من الـ image القديمة ما زالت تحتويه.</p>\n","checkLabels":{}},"words":730,"readMinutes":4}