{"id":"l-dk-3-1","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"صِف notes-api وPostgres في ملف compose.yaml واحد، وشغّل البيئة وافحصها بـ <code dir=\"ltr\">docker compose</code>، واربط الخدمات بأسمائها عبر الشبكة الافتراضية التي ينشئها Compose.","html":"<p>يحتاج notes-api إلى Postgres. تستطيع تشغيل الاثنين بأمرَي <code dir=\"ltr\">docker run</code>، وشبكة مصنوعة يدويًا، وصفحة من الخيارات، ثم تشرح ذلك كله من جديد لأول شخص ينضمّ إلى الفريق. ‏Compose يستبدل ذلك بملف واحد في المستودع يصف البيئة كاملة، وأمر واحد يحوّلها إلى واقع.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">أول compose.yaml</h2>\n<p>أنشئ <code dir=\"ltr\">compose.yaml</code> بجوار الـ Dockerfile:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">compose.yaml</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:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">services</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  api</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    build</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api:dev</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      - </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"8080:3000\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      PORT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"3000\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      DATABASE_URL</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">postgres://notes:notes@db:5432/notes</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  db</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">postgres:17</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      POSTGRES_USER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      POSTGRES_PASSWORD</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      POSTGRES_DB</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span></code></pre></div>\n<p>خدمتان. ‏<code dir=\"ltr\">api</code> تُبنى من الـ Dockerfile في هذا المجلد وتحمل الـ tag ‏<code dir=\"ltr\">notes-api:dev</code>؛ و<code dir=\"ltr\">db</code> تستخدم الـ image الرسمية لـ Postgres 17، التي تنشئ نقطة دخولها المستخدم وقاعدة البيانات من هذه المتغيّرات الثلاثة عند أول تشغيل. و<code dir=\"ltr\">name: notes</code> يضبط اسم المشروع، الذي يُضاف كبادئة إلى كل ما ينشئه Compose.</p>\n<p>حين تحتوي الخدمة على <code dir=\"ltr\">build:</code> و<code dir=\"ltr\">image:</code> معًا، يبني Compose من الـ Dockerfile ويعطي النتيجة الاسم الموجود في <code dir=\"ltr\">image:</code>. هذا يمنح الـ image المحلية اسمًا متوقّعًا تستطيع تشغيله يدويًا أيضًا، بدلًا من الاسم الافتراضي الذي كان Compose سيولّده من اسمَي المشروع والخدمة.</p>\n<p>لا يوجد سطر <code dir=\"ltr\">version:</code>. ‏Compose v2 يطبّق مواصفة Compose ‏(Compose Specification)، وإذا نسخت ملفًا قديمًا يبدأ بـ <code dir=\"ltr\">version: &quot;3.8&quot;</code>، فسيتجاهله Compose ويحذّر من أن الخاصية قديمة. احذفه.</p>\n<p>الأمر هو <code dir=\"ltr\">docker compose</code>، بمسافة. هذا Compose v2، إضافة لواجهة Docker CLI. أما <code dir=\"ltr\">docker-compose</code> القديم المكتوب بـ Python والموصول بشرطة فقد تقاعد منذ سنوات، والدروس التي تستخدمه قديمة من نواحٍ أخرى أيضًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">شغّل البيئة</h2>\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\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> up</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --build</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ps</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> logs</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">curl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> localhost:8080/readyz</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">up</code> يبني الـ image إن لزم، وينشئ شبكة، ويشغّل كلا الـ containers، ثم يعود. و<code dir=\"ltr\">--build</code> يفرض إعادة البناء حتى تُلتقط تغييرات الكود. والآن يجيب <code dir=\"ltr\">/readyz</code> بـ <code dir=\"ltr\">ready</code>، لأن الـ API يستطيع الوصول إلى قاعدة البيانات.</p>\n<p>الأمر <code dir=\"ltr\">docker compose ps</code> يعرض الـ containers التي أنشأها Compose، مسمّاة باسم المشروع والخدمة ورقم: <code dir=\"ltr\">notes-api-1</code> و<code dir=\"ltr\">notes-db-1</code>. الرقم موجود لأن الخدمة الواحدة قد تشغّل عدّة containers متطابقة. وكل الأوامر تأخذ أسماء الخدمات لا أسماء الـ containers، فنادرًا ما تكتب الاسم الكامل.</p>\n<p>أوامر أخرى ستستخدمها يوميًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><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\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> exec</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> db</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> psql</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -U</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">   # SQL shell in the db container</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> restart</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> api</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">                       # restart one service</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> down</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">                              # stop and remove containers and network</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">up</code> <strong>تصريحي</strong> (declarative): يقارن الملف بما يعمل، ولا يغيّر إلا الفرق. عدّل متغيّرات بيئة الـ API وشغّل <code dir=\"ltr\">up -d</code> مجددًا، فيعيد Compose إنشاء <code dir=\"ltr\">api</code> بينما يستمر <code dir=\"ltr\">db</code> في العمل. ‏Kubernetes يعمل بالفكرة نفسها على نطاق أكبر بكثير؛ وستقابله في القسم الرابع.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">كيف تجد الخدمات بعضها</h2>\n<p>ينشئ Compose شبكة للمشروع، <code dir=\"ltr\">notes_default</code>، ويربط كل خدمة بها. على تلك الشبكة، يحلّ DNS الخاص بـ Docker كل <strong>اسم خدمة</strong> إلى الـ container الخاص بها. لهذا يقول <code dir=\"ltr\">DATABASE_URL</code> ‏<code dir=\"ltr\">@db:5432</code>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-dk-3-1-t1\"><defs><marker id=\"arrow-l-dk-3-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-dk-3-1-t1\">جهازك يصل إلى الـ container ‏api عبر المنفذ المنشور 8080 المربوط بـ 3000. داخل الشبكة notes_default، يتّصل api بـ db على المنفذ 5432 باسم الخدمة. قاعدة البيانات ليس لها منفذ منشور.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"80\" width=\"140\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"90\" y=\"110\" text-anchor=\"middle\">جهازك</text>\n  <text class=\"d-code\" x=\"90\" y=\"134\" text-anchor=\"middle\">localhost:8080</text>\n  <rect class=\"d-box-primary\" x=\"210\" y=\"20\" width=\"470\" height=\"190\" rx=\"14\"/>\n  <text class=\"d-label-muted\" x=\"445\" y=\"44\" text-anchor=\"middle\">الشبكة: notes_default</text>\n  <rect class=\"d-box-accent\" x=\"250\" y=\"80\" width=\"160\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"330\" y=\"110\" text-anchor=\"middle\">api</text>\n  <text class=\"d-code\" x=\"330\" y=\"134\" text-anchor=\"middle\">:3000</text>\n  <rect class=\"d-box-success\" x=\"490\" y=\"80\" width=\"160\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"570\" y=\"110\" text-anchor=\"middle\">db</text>\n  <text class=\"d-code\" x=\"570\" y=\"134\" text-anchor=\"middle\">:5432</text>\n  <path class=\"d-arrow\" d=\"M160 115 L248 115\" marker-end=\"url(#arrow-l-dk-3-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"204\" y=\"104\" text-anchor=\"middle\">-p 8080:3000</text>\n  <path class=\"d-arrow\" d=\"M410 115 L488 115\" marker-end=\"url(#arrow-l-dk-3-1-1)\"/>\n  <text class=\"d-code\" x=\"449\" y=\"104\" text-anchor=\"middle\">db:5432</text>\n  <text class=\"d-label-muted\" x=\"570\" y=\"180\" text-anchor=\"middle\">لا منفذ منشور</text>\n</svg><figcaption>الـ API وحده منشور؛ وقاعدة البيانات يُوصل إليها بالاسم داخل شبكة المشروع</figcaption></figure>\n<p>لاحظ أن <code dir=\"ltr\">db</code> ليس فيه مدخل <code dir=\"ltr\">ports:</code>. يصل إليه الـ API عبر شبكة المشروع؛ ولا يستطيع ذلك أي شيء على حاسوبك أو على شبكة مكتبك. إن أردت توجيه عميل SQL مكتبي إليه، فانشره على الـ loopback فقط: <code dir=\"ltr\">&quot;127.0.0.1:5432:5432&quot;</code>.</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>استخدام localhost بين الـ containers</span></p>\n<p>العنوان <code dir=\"ltr\">postgres://…@localhost:5432</code> هو أكثر خطأ أصادفه في ملفات Compose. داخل الـ container ‏<code dir=\"ltr\">api</code>، تعني <code dir=\"ltr\">localhost</code> الـ container الخاص بالـ API نفسه، ولا شيء يستمع على 5432 هناك. والخطأ هو <code dir=\"ltr\">ECONNREFUSED 127.0.0.1:5432</code>. استخدم اسم الخدمة.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">فصل الشبكات حين يهمّ ذلك</h2>\n<p>في البيئات الأكبر، تستطيع التصريح بالشبكات وربط كل خدمة بما يخصّها فقط. قاعدة البيانات على شبكة <code dir=\"ltr\">backend</code> لا يمكن أن تصل إليها واجهة ويب أمامية لا تنضمّ إلا إلى <code dir=\"ltr\">frontend</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">YAML</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:#116329;--shiki-dark:#7EE787\">services</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  web</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    networks</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">frontend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  api</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    networks</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">frontend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">backend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  db</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    networks</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">backend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">networks</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  frontend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  backend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span></code></pre></div>\n<p>لـ notes-api تكفي الشبكة الافتراضية. الجأ إلى هذا حين تحتوي البيئة على مكوّنات يجب ألا يتحدّث بعضها إلى بعض أبدًا.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">أين يناسب Compose</h2>\n<p>‏Compose ممتاز لثلاث مهام: التطوير المحلي، واختبارات التكامل في الـ CI (شغّل البيئة، ونفّذ الاختبارات، ثم فكّكها)، والخدمات الصغيرة التي تعيش على مضيف واحد. ما لا يفعله هو توزيع الـ containers على أجهزة متعدّدة، أو نقلها حين يموت جهاز، أو نشر إصدار جديد تدريجيًا مع مراقبة فحوص الصحة. هذه مهام المنسّق (orchestrator)، وهي ما يفعله Kubernetes في القسم الرابع. والعادات التي تبنيها هنا تنتقل معك مباشرة: صرّح بالحالة المرغوبة في ملف، ودع أداة تحقّقها، واربط الخدمات بأسمائها.</p>\n<p>تبقى مشكلة واحدة. شغّل <code dir=\"ltr\">docker compose down</code>، ثم <code dir=\"ltr\">up</code> مجددًا، فتجد أن كل الملاحظات اختفت: خزّن Postgres بياناته في الـ layer القابلة للكتابة داخل الـ container، والتي حذفها <code dir=\"ltr\">down</code>. الدرس التالي يعطي قاعدة البيانات مكانًا دائمًا تعيش فيه.</p>\n","toc":[{"id":"sec-1","text":"أول compose.yaml","level":2},{"id":"sec-2","text":"شغّل البيئة","level":2},{"id":"sec-3","text":"كيف تجد الخدمات بعضها","level":2},{"id":"sec-4","text":"فصل الشبكات حين يهمّ ذلك","level":2},{"id":"sec-5","text":"أين يناسب Compose","level":2}],"takeaways":["ملف <code dir=\"ltr\">compose.yaml</code> يصرّح بالخدمات والشبكات والـ volumes؛ والأمر <code dir=\"ltr\">docker compose up</code> ينشئ كل ما هو ناقص ويترك الباقي كما هو.","‏Compose v2 هو الإضافة <code dir=\"ltr\">docker compose</code>؛ والمفتاح <code dir=\"ltr\">version:</code> في المستوى الأعلى قديم ويجب حذفه.","كل خدمة تنضمّ إلى الشبكة الافتراضية للمشروع ويمكن الوصول إليها باسمها، لذا يتّصل الـ API بـ <code dir=\"ltr\">db:5432</code> لا بـ <code dir=\"ltr\">localhost</code>.","انشر على المضيف المنافذ التي تحتاج إليها فقط؛ وقاعدة البيانات التي لا يتحدّث معها إلا الـ API لا تحتاج إلى مدخل <code dir=\"ltr\">ports:</code> إطلاقًا."],"further":[{"title":"How Compose works","url":"https://docs.docker.com/compose/intro/compose-application-model/"},{"title":"Networking in Compose","url":"https://docs.docker.com/compose/how-tos/networking/"},{"title":"Compose file reference","url":"https://docs.docker.com/reference/compose-file/"}],"quiz":[{"q":"<p>داخل الـ container ‏<code dir=\"ltr\">api</code>، أي مضيف في <code dir=\"ltr\">DATABASE_URL</code> يصل إلى خدمة Postgres المسمّاة <code dir=\"ltr\">db</code>؟</p>","options":[{"text":"الاسم <code dir=\"ltr\">db</code>، اسم الخدمة، الذي يحلّه DNS شبكة Compose إلى container قاعدة البيانات.","why":"صحيح. الخدمات على شبكة Compose نفسها يجد بعضها بعضًا باسم الخدمة."},{"text":"‏<code dir=\"ltr\">localhost</code>، لأن كلا الـ containers يعمل على الجهاز نفسه.","why":"لكل container ‏network namespace خاصة به. ‏<code dir=\"ltr\">localhost</code> داخل <code dir=\"ltr\">api</code> هو container الـ API نفسه، ولا شيء يستمع فيه على 5432."},{"text":"‏<code dir=\"ltr\">host.docker.internal</code>، الذي يشير دائمًا إلى الـ containers الأخرى.","why":"هذا الاسم يشير إلى جهازك المضيف. لا يعمل إلا إذا كان منفذ قاعدة البيانات منشورًا هناك، ويربط التطبيق بحاسوبك."},{"text":"عنوان IP الخاص بـ container قاعدة البيانات من <code dir=\"ltr\">docker inspect</code>.","why":"يعمل إلى أن يُعاد إنشاء الـ container ويحصل على IP جديد. الأسماء ثابتة؛ وعناوين IP ليست كذلك."}],"answer":0},{"q":"<p>تضيف <code dir=\"ltr\">version: &quot;3.8&quot;</code> في أعلى <code dir=\"ltr\">compose.yaml</code>. ماذا يفعل Docker Compose الحالي بها؟</p>","options":[{"text":"يتحوّل إلى قواعد صيغة الملف 3.8.","why":"يطبّق Compose v2 مواصفة Compose الموحّدة، ولم يعد يختار السلوك حسب الإصدار."},{"text":"يفشل، لأن مفاتيح الإصدار لم تعد مسموحة.","why":"لا يفشل. يحذّر من أن الخاصية قديمة ويتجاهلها."},{"text":"يتجاهلها ويطبع تحذيرًا بأن <code dir=\"ltr\">version</code> قديمة.","why":"صحيح. احذف السطر؛ فهو لا يفعل شيئًا."},{"text":"ينزّل إصدار Compose المطابق.","why":"لا يجلب Compose نفسه أبدًا بناءً على الملف. الإضافة <code dir=\"ltr\">docker compose</code> المثبّتة لديك هي التي تعمل."}],"answer":2},{"q":"<p>قاعدة Postgres في بيئة Compose لديك لا يستخدمها إلا <code dir=\"ltr\">api</code>. أي إعداد <code dir=\"ltr\">ports:</code> يجب أن يكون لـ <code dir=\"ltr\">db</code>؟</p>","options":[{"text":"<code dir=\"ltr\">&quot;5432:5432&quot;</code>، حتى يصل إليها الـ API.","why":"يصل إليها الـ API عبر شبكة Compose دون أي منفذ منشور. والنشر يعرضها على كل واجهات المضيف."},{"text":"<code dir=\"ltr\">&quot;0.0.0.0:5432:5432&quot;</code>، للتصريح بالاستماع في كل مكان.","why":"هذا مطابق للافتراضي، ويضع قاعدة بياناتك على شبكة Wi-Fi المكتب مع كلمة مرور تطوير."},{"text":"<code dir=\"ltr\">&quot;5432&quot;</code>، الذي ينشرها على منفذ عشوائي في المضيف.","why":"ما زال يمكن الوصول إليها من شبكة المضيف، فقط على منفذ أقل وضوحًا."},{"text":"لا شيء إطلاقًا؛ أضف <code dir=\"ltr\">&quot;127.0.0.1:5432:5432&quot;</code> فقط إن احتجت إلى عميل رسومي محلي.","why":"صحيح. الحركة بين الـ containers لا تحتاج إلى منافذ منشورة، والنشر على الـ loopback فقط يُبقيها خارج الشبكة."}],"answer":3},{"q":"<p>تغيّر متغيّرات البيئة لخدمة <code dir=\"ltr\">api</code> فقط في <code dir=\"ltr\">compose.yaml</code> وتشغّل <code dir=\"ltr\">docker compose up -d</code>. ماذا يحدث للـ container ‏<code dir=\"ltr\">db</code> الذي يعمل؟</p>","options":[{"text":"يُعاد إنشاؤه هو أيضًا، لأن الملف تغيّر.","why":"يقارن Compose إعدادات كل خدمة على حدة. ‏<code dir=\"ltr\">db</code> لم يتغيّر، فلا يُمسّ."},{"text":"يستمر في العمل دون أن يُمسّ؛ لا يعيد Compose إنشاء إلا الخدمات التي تغيّرت إعداداتها.","why":"صحيح. ‏<code dir=\"ltr\">up</code> يوفّق كل خدمة مع الملف ويترك الخدمات غير المتغيّرة وشأنها."},{"text":"يتوقّف ويحتاج إلى <code dir=\"ltr\">docker compose start db</code> منفصل.","why":"الأمر <code dir=\"ltr\">up</code> لا يوقف الخدمات التي هي أصلًا في الحالة المرغوبة."},{"text":"يُعلَّق مؤقتًا بينما يُعاد تشغيل <code dir=\"ltr\">api</code>، ثم يُستأنف.","why":"لا يعلّق Compose الاعتماديات أثناء إعادة الإنشاء."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"yaml","code":"services:\n  api:\n    build: .\n    ports:\n      - \"8080:3000\"\n    environment:\n      DATABASE_URL: postgres://notes:notes@localhost:5432/notes\n  db:\n    image: postgres:17\n    ports:\n      - \"5432:5432\"\n    environment:\n      POSTGRES_USER: notes\n      POSTGRES_PASSWORD: notes\n      POSTGRES_DB: notes","bugLines":[7,11],"title":"خطآن في Compose، واتصال واحد","prompt":"<p>مع ملف <code dir=\"ltr\">compose.yaml</code> هذا، يكتب الـ API في سجلّه <code dir=\"ltr\">ECONNREFUSED 127.0.0.1:5432</code>، وزميل على شبكة Wi-Fi المكتب اتّصل للتوّ بقاعدة بياناتك بكلمة المرور <code dir=\"ltr\">notes</code>. اختر السطرين <strong>الاثنين</strong> المسؤولين.</p>\n","hints":["داخل الـ container ‏<code dir=\"ltr\">api</code>، إلى ماذا يشير <code dir=\"ltr\">localhost</code>؟","هل يحتاج الـ API إلى منفذ منشور للوصول إلى قاعدة البيانات عبر شبكة Compose؟"],"explanation":"<p>السطر 7 يوجّه الـ API إلى <code dir=\"ltr\">localhost</code>، و<code dir=\"ltr\">localhost</code> داخل container الـ API هو الـ API نفسه. استخدم اسم الخدمة: <code dir=\"ltr\">postgres://notes:notes@db:5432/notes</code>.</p>\n<p>السطر 11 ينشر Postgres على كل واجهات المضيف، فيستطيع أي شخص على شبكتك الوصول إليه. الـ API لا يحتاج إلى ذلك؛ فالـ containers تتحدّث عبر شبكة المشروع. احذف كتلة <code dir=\"ltr\">ports:</code>، أو اربط على الـ loopback إن احتجت إلى عميل SQL محلي:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">YAML</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:#116329;--shiki-dark:#7EE787\">    ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      - </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"127.0.0.1:5432:5432\"</span></span></code></pre></div>\n","checkLabels":{}},"words":632,"readMinutes":4}