الوحدة 3 · بيئات محلية مع Docker Compose
أساسيات Compose: الخدمات والشبكات
18 دقراءة 4 داكتشف الخطأ
الدرس في لمحة
صِف notes-api وPostgres في ملف compose.yaml واحد، وشغّل البيئة وافحصها بـ docker compose، واربط الخدمات بأسمائها عبر الشبكة الافتراضية التي ينشئها Compose.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
يحتاج notes-api إلى Postgres. تستطيع تشغيل الاثنين بأمرَي docker run، وشبكة مصنوعة يدويًا، وصفحة من الخيارات، ثم تشرح ذلك كله من جديد لأول شخص ينضمّ إلى الفريق. Compose يستبدل ذلك بملف واحد في المستودع يصف البيئة كاملة، وأمر واحد يحوّلها إلى واقع.
أول compose.yaml
أنشئ compose.yaml بجوار الـ Dockerfile:
name: notes
services:
api:
build: .
image: notes-api:dev
ports:
- "8080:3000"
environment:
PORT: "3000"
DATABASE_URL: postgres://notes:notes@db:5432/notes
db:
image: postgres:17
environment:
POSTGRES_USER: notes
POSTGRES_PASSWORD: notes
POSTGRES_DB: notesخدمتان. api تُبنى من الـ Dockerfile في هذا المجلد وتحمل الـ tag notes-api:dev؛ وdb تستخدم الـ image الرسمية لـ Postgres 17، التي تنشئ نقطة دخولها المستخدم وقاعدة البيانات من هذه المتغيّرات الثلاثة عند أول تشغيل. وname: notes يضبط اسم المشروع، الذي يُضاف كبادئة إلى كل ما ينشئه Compose.
حين تحتوي الخدمة على build: وimage: معًا، يبني Compose من الـ Dockerfile ويعطي النتيجة الاسم الموجود في image:. هذا يمنح الـ image المحلية اسمًا متوقّعًا تستطيع تشغيله يدويًا أيضًا، بدلًا من الاسم الافتراضي الذي كان Compose سيولّده من اسمَي المشروع والخدمة.
لا يوجد سطر version:. Compose v2 يطبّق مواصفة Compose (Compose Specification)، وإذا نسخت ملفًا قديمًا يبدأ بـ version: "3.8"، فسيتجاهله Compose ويحذّر من أن الخاصية قديمة. احذفه.
الأمر هو docker compose، بمسافة. هذا Compose v2، إضافة لواجهة Docker CLI. أما docker-compose القديم المكتوب بـ Python والموصول بشرطة فقد تقاعد منذ سنوات، والدروس التي تستخدمه قديمة من نواحٍ أخرى أيضًا.
شغّل البيئة
docker compose up -d --build
docker compose ps
docker compose logs -f api
curl localhost:8080/readyzالأمر up يبني الـ image إن لزم، وينشئ شبكة، ويشغّل كلا الـ containers، ثم يعود. و--build يفرض إعادة البناء حتى تُلتقط تغييرات الكود. والآن يجيب /readyz بـ ready، لأن الـ API يستطيع الوصول إلى قاعدة البيانات.
الأمر docker compose ps يعرض الـ containers التي أنشأها Compose، مسمّاة باسم المشروع والخدمة ورقم: notes-api-1 وnotes-db-1. الرقم موجود لأن الخدمة الواحدة قد تشغّل عدّة containers متطابقة. وكل الأوامر تأخذ أسماء الخدمات لا أسماء الـ containers، فنادرًا ما تكتب الاسم الكامل.
أوامر أخرى ستستخدمها يوميًا:
docker compose exec db psql -U notes -d notes # SQL shell in the db container
docker compose restart api # restart one service
docker compose down # stop and remove containers and networkالأمر up تصريحي (declarative): يقارن الملف بما يعمل، ولا يغيّر إلا الفرق. عدّل متغيّرات بيئة الـ API وشغّل up -d مجددًا، فيعيد Compose إنشاء api بينما يستمر db في العمل. Kubernetes يعمل بالفكرة نفسها على نطاق أكبر بكثير؛ وستقابله في القسم الرابع.
كيف تجد الخدمات بعضها
ينشئ Compose شبكة للمشروع، notes_default، ويربط كل خدمة بها. على تلك الشبكة، يحلّ DNS الخاص بـ Docker كل اسم خدمة إلى الـ container الخاص بها. لهذا يقول DATABASE_URL @db:5432.
لاحظ أن db ليس فيه مدخل ports:. يصل إليه الـ API عبر شبكة المشروع؛ ولا يستطيع ذلك أي شيء على حاسوبك أو على شبكة مكتبك. إن أردت توجيه عميل SQL مكتبي إليه، فانشره على الـ loopback فقط: "127.0.0.1:5432:5432".
فصل الشبكات حين يهمّ ذلك
في البيئات الأكبر، تستطيع التصريح بالشبكات وربط كل خدمة بما يخصّها فقط. قاعدة البيانات على شبكة backend لا يمكن أن تصل إليها واجهة ويب أمامية لا تنضمّ إلا إلى frontend:
services:
web:
networks: [frontend]
api:
networks: [frontend, backend]
db:
networks: [backend]
networks:
frontend:
backend:لـ notes-api تكفي الشبكة الافتراضية. الجأ إلى هذا حين تحتوي البيئة على مكوّنات يجب ألا يتحدّث بعضها إلى بعض أبدًا.
أين يناسب Compose
Compose ممتاز لثلاث مهام: التطوير المحلي، واختبارات التكامل في الـ CI (شغّل البيئة، ونفّذ الاختبارات، ثم فكّكها)، والخدمات الصغيرة التي تعيش على مضيف واحد. ما لا يفعله هو توزيع الـ containers على أجهزة متعدّدة، أو نقلها حين يموت جهاز، أو نشر إصدار جديد تدريجيًا مع مراقبة فحوص الصحة. هذه مهام المنسّق (orchestrator)، وهي ما يفعله Kubernetes في القسم الرابع. والعادات التي تبنيها هنا تنتقل معك مباشرة: صرّح بالحالة المرغوبة في ملف، ودع أداة تحقّقها، واربط الخدمات بأسمائها.
تبقى مشكلة واحدة. شغّل docker compose down، ثم up مجددًا، فتجد أن كل الملاحظات اختفت: خزّن Postgres بياناته في الـ layer القابلة للكتابة داخل الـ container، والتي حذفها down. الدرس التالي يعطي قاعدة البيانات مكانًا دائمًا تعيش فيه.
أهم النقاط
- ملف
compose.yamlيصرّح بالخدمات والشبكات والـ volumes؛ والأمرdocker compose upينشئ كل ما هو ناقص ويترك الباقي كما هو. - Compose v2 هو الإضافة
docker compose؛ والمفتاحversion:في المستوى الأعلى قديم ويجب حذفه. - كل خدمة تنضمّ إلى الشبكة الافتراضية للمشروع ويمكن الوصول إليها باسمها، لذا يتّصل الـ API بـ
db:5432لا بـlocalhost. - انشر على المضيف المنافذ التي تحتاج إليها فقط؛ وقاعدة البيانات التي لا يتحدّث معها إلا الـ API لا تحتاج إلى مدخل
ports:إطلاقًا.