3.3 · مثال مسلسل : تطبيق وب + قاعدة بيانات

المستوى 3 · دوكر كومبوز (Docker Compose)

3.3مثال مسلسل : تطبيق وب + قاعدة بيانات

الهدف: تشغيل تطبيق مصغر حقيقي (API + قاعدة) مربوط بالأسماء.
الوقت المقدّر: 15 دقيقة · قراءة ذاتية · إذا كان لديك 5 دقائق فقط: اقرأ «الأساسي في 30 ثانية» + المخطط.

الأساسي في 30 ثانية

  • ثلاث خدمات، وشبكة واحدة : الواجهة api (بناء محلي)، والقاعدة bdd (postgres + مجلد)، وadminer (وحدة تحكم التطوير) — فالواجهة تصل bdd:5432 بالاسم، دون IP ثابت.
  • رابط الاتصال يسافر في متغير : DATABASE_URL تقرؤه الواجهة — ولا مضيف ثابت في الكود، فالإعداد يتغير دون مساس الصورة.
  • depends_on يرتب، وإعادة المحاولة توثق : فالقاعدة تبدأ أبطأ — والواجهة تعيد محاولة اتصالها عند بدء التشغيل، وإلا فالإطلاق الأول يتحطم.

حالة مصغرة مسلسلة — إدير يربط نموذج الفريق

إدير، ديفوبس مبتدئ في الجزائر، عليه تقديم نموذج API + قاعدة يطلقه كل واحد بأمر واحد : فمحاولته الأولى تضع IP القاعدة ثابتا وتتحطم مرة من اثنتين عند بدء التشغيل. يمر إلى الربط بالأسماء (تصل الواجهة bdd:5432)، والرابط في متغير DATABASE_URL، والمجلد الدائم — ويضيف حلقة إعادة محاولة جهة الواجهة. النتيجة : up -d --build، والواجهة ترد على :3000، و adminer على :8081، والقاعدة لا تُوصَل إلا من الداخل ؛ ثم down فـ up : فالجدول المُنشَأ أمس ما زال موجودا.

هذه الحالة نفسها تعود في الدرس والمخطط والنص الجاهز وتحدي 48 ساعة.

1. ثلاث خدمات وشبكة واحدة وأسماء — لا عناوين IP

التطبيق الحقيقي هو الواجهة api (صورتكم، وبناء محلي من ./api)، والقاعدة bdd (postgres + مجلد مسمى) وadminer (وحدة تحكم وب خفيفة لإدارة SQL، منشورة في التطوير). يربطها كومبوز على الشبكة نفسها : فتصل الواجهة bdd:5432 باسم الخدمة — فالعناوين IP تتغير عند كل إعادة إنشاء، والأسماء تبقى. ولا IP ثابت أبدا في الكود : إنها القاعدة التي تجعل الستاك محمولا.

وتقرأ الواجهة DATABASE_URL في متغير (المضيف = bdd، والقاعدة، والمعرفات) : فيسافر الإعداد، ويبقى الكود متماثلا بين التطوير والإنتاج. وننشر الواجهة (:3000) و adminer ‏(:8081، والتطوير فقط)‏ ؛ أما القاعدة فلا تُنشَر أبدا — فلا تُوصَل إلا من الداخل. سطح معروض أدنى : وحده ما يجب أن يخرج يخرج.

2. الترتيب لا يكفي : إعادة المحاولة واجبة، والديمومة مثبتة

يطلق depends_on القاعدة قبل الواجهة، لكن ليس توفرها : فيحتاج postgres ثواني عدة لقبول الاتصالات. ودون حلقة إعادة محاولة عند بدء التشغيل، تتحطم الواجهة عند الإطلاق الأول ثم « تنجح » في الثاني — العرض الكلاسيكي. فالفرق بين عرض وستاك ينجو من إعادة التشغيل هو إعادة المحاولة هذه مع مهلة جهة التطبيق (وفحص الصحة ينظم زيادة على ذلك، المستوى 4.2).

والبرهان على تماسك كل شيء : down ثم up — فيجب أن ينجو الجدول المُنشَأ عبر adminer، لأنه يعيش في المجلد، لا في الحاوية. ويجعل إدير منه اختبار استلامه المنهجي : فما لا ينجو من إعادة التشغيل ليس دائما، نقطة. فقاعدة دائمة، وغير منشورة أبدا، وإعادة محاولة عند بدء التشغيل : ثلاثي الستاك السليم.

خطأ شائع في الفهم: « يكفي depends_on للتوفر، ويساعد نشر القاعدة في التنقيح ». لا، مرتين. فـ depends_on يرتب دون انتظار : فدون إعادة محاولة، يتحطم بدء التشغيل الأول — وذلك طبيعي ومتوقع، وليس لغزا. ونشر postgres على المضيف (خصوصا في الإنتاج) يوسع سطح الهجوم لراحة وهمية : فننقح عبر adminer في التطوير و exec في الإنتاج، ولا نعرض القاعدة أبدا.
تطبيق مربوط ودائم ومقتصد الأسماء والمتغير وإعادة المحاولة والسطح الأدنى الربط بالأسماءالواجهة api ← bdd:5432، والرابط في DATABASE_URL+التديم + إعادة المحاولةمجلد مسمى، وحلقة إعادة اتصال+عرض الأدنىالواجهة و adminer (التطوير) منشوران، والقاعدة داخلية يثبت down/up الديمومة ؛ وتثبت إعادة المحاولة المتانة.
الأسماء والمتغير وإعادة المحاولة والسطح الأدنى
نص المخطط (SEO / قارئات الشاشة / الترجمة): 1) الربط : يجعل إدير الواجهة تصل bdd:5432 بالاسم عبر DATABASE_URL بدل IP ثابت. 2) التوثيق : إعادة محاولة عند بدء التشغيل جهة الواجهة (فـ depends_on يرتب، وإعادة المحاولة تنتظر حقا) + مجلد مسمى للقاعدة. 3) العرض : الواجهة و adminer منشوران، والقاعدة داخلية فقط — ويثبت down/up نجاة الجدول. الدرس : الأسماء والمتغير وإعادة المحاولة والسطح الأدنى.

🔎 مثال ملموس — ستاك API + Postgres‏ + Adminer، مربوط ودائم

يكتب إدير كومبوز النموذج : متغيرات ومجلد وشبكة، و adminer اختياري في التطوير.

services:
  api:
    build: ./api
    ports: ["3000:3000"]
    environment:
      DATABASE_URL: ${DATABASE_URL}
    depends_on: [bdd]
    networks: [appnet]
  bdd:
    image: postgres:16.4
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-app}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB:-appdb}
    volumes: [pgdata:/var/lib/postgresql/data]
    networks: [appnet]
  adminer:
    image: adminer:4.8
    profiles: [dev]
    ports: ["8081:8080"]
    networks: [appnet]
volumes:
  pgdata:
networks:
  appnet:
# الإطلاق : docker compose --profile dev up -d --build

القراءة : الواجهة و adminer منشوران، والقاعدة ليست كذلك — فلا تُوصَل إلا من الداخل.

الدرس : افتحوا :3000 (الواجهة) و :8081 (adminer) ؛ ويثبت down/up نجاة الجدول.

مفردات القسم

DATABASE_URL
رابط الاتصال الكامل الممرر في متغير : المضيف = اسم الخدمة، ولا IP ثابت أبدا.

مثال بسيط: واجهة إدير تقرأ ${DATABASE_URL} المشيرة إلى bdd.

ما ليس كذلك: الـ IP الموجود أمس في inspect، منسوخ في الكود.

رد فعل: المضيف = اسم الخدمة، والإعداد = متغير.

إعادة المحاولة (Retry)
حلقة إعادة الاتصال عند بدء التشغيل : تعيد الواجهة المحاولة حتى تقبل القاعدة.

مثال بسيط: إعادة المحاولة التي تشفي تحطم بدء التشغيل الأول.

ما ليس كذلك: اتصال وحيد يفشل إذا كانت القاعدة بطيئة.

رد فعل: كل اتصال عند بدء التشغيل = إعادة محاولة مع مهلة.

Adminer
وحدة تحكم وب خفيفة لإدارة SQL، منشورة في التطوير لرؤية القاعدة وملئها.

مثال بسيط: ينشئ إدير جدول الاختبار على :8081.

ما ليس كذلك: القاعدة منشورة في الإنتاج « للتنقيح ».

رد فعل: وحدة التحكم في التطوير فقط، ولا في الإنتاج أبدا.

بناء محلي (Build local)
خدمة مبنية من مجلد (./api) بدل صورة بعيدة : كودكم مُحوَى.

مثال بسيط: صورة الواجهة المبنية عند up --build.

ما ليس كذلك: كود مركب يدويا في حاوية nginx.

رد فعل: --build بعد كل تغيير كود.

السطح المعروض
مجموع المنافذ المنشورة : نقلله — وحده الضروري يخرج.

مثال بسيط: الواجهة + adminer (التطوير) منشوران، و bdd داخلية.

ما ليس كذلك: postgres منشور على المضيف « تحسبا ».

رد فعل: كل منفذ منشور يجب أن يبرر وجوده.

في التطبيق — تركيب التطبيق المصغر المربوط والدائم

  1. أنشئوا ./api ‏(Dockerfile + خادم يقرأ DATABASE_URL مع إعادة محاولة عند بدء التشغيل).
  2. اكتبوا كومبوز أعلاه (الرابط في متغير، والقاعدة غير منشورة) واطلقوا up -d --build.
  3. تحققوا من الواجهة (:3000) و adminer ‏(:8081، والملف الشخصي dev)‏، وأنشئوا جدولا.
  4. أعيدوا تشغيل كل شيء (down ثم up) : فيجب أن ينجو الجدول — وإلا فالمجلد مركب خطأ.
يدور تطبيق حقيقي متعدد الخدمات، مربوط بالأسماء، ودائم وبسطح أدنى.

نص جاهز للقول — تسليم النموذج للفريق (30 ثانية)

« أمر واحد : up -d --build مع الملف الشخصي dev. ترد الواجهة على 3000، ووحدة تحكم القاعدة على 8081، ولا تُوصَل القاعدة إلا من الداخل. والرابط يسافر في متغير، وإعادة المحاولة تمتص بدء التشغيل، ولا يفقد down/up شيئا — مُختبَر. »

يُقال عند تسليم النموذج ؛ واعرضوا down/up والجدول سليما.

نموذج نسخ-لصق — API + قاعدة + وحدة تحكم (صالح)

services:
  api:
    build: ./api
    ports:
      - "${PORT:-3000}:3000"
    environment:
      DATABASE_URL: ${DATABASE_URL}
    depends_on:
      - bdd
    networks:
      - appnet
  bdd:
    image: postgres:16.4
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-app}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB:-appdb}
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - appnet
  adminer:
    image: adminer:4.8
    profiles:
      - dev
    ports:
      - "8081:8080"
    networks:
      - appnet

volumes:
  pgdata:

networks:
  appnet:
# .env محلي (غير مُلتزَم به) : DATABASE_URL و POSTGRES_PASSWORD

مستقل

الربط بالأسماء + الرابط في متغير ونموذجكم يطلق عند كل عميل.

مقاولة صغيرة في الجزائر

يسلم إدير النموذج بأمر واحد : الواجهة على 3000، ووحدة التحكم على 8081، والقاعدة دائمة.

فريق منتج

توحيد DATABASE_URL + إعادة المحاولة عند بدء التشغيل لكل خدمات الفريق.

مبتدئ

هذا الأسبوع : واجهة + قاعدة مربوطتان بالأسماء، وجدول مُنشَأ، و down/up يثبت الديمومة.

حذر: كلمات السر ورابط الاتصال عبر .env المحلي، ولا تُكتَب أبدا ثابتة : ${DATABASE_URL} و ${POSTGRES_PASSWORD} في كومبوز، والقيم الحقيقية فقط في .env غير المُلتزَم به. وفي الإنتاج : سر مُدار (ملف، أو CI، أو منسق) — ولا كومبوز مُلتزَم به مكشوف أبدا، ولا قاعدة منشورة أبدا.

✓ ممارسات جيدة

  • صِلوا بأسماء الخدمات، واضبطوا بمتغيرات البيئة.
  • أضيفوا إعادة محاولة اتصال عند بدء تشغيل كل خدمة زبونة للقاعدة.
  • أبقوا وحدة تحكم الإدارة في التطوير فقط (الملف الشخصي dev)، وسطحا أدنى في الإنتاج.

✗ أخطاء شائعة

  • IP ثابت في الكود : فيتغير عند كل إعادة إنشاء.
  • الاعتقاد بأن depends_on = جاهز : فدون إعادة محاولة، يتحطم بدء التشغيل الأول.
  • نشر القاعدة في الإنتاج : سطح هجوم موسع دون فائدة.

النقاط الأساسية

  • شبكة واحدة وأسماء ورابط في متغير : bdd:5432 عبر DATABASE_URL، ولا IP أبدا.
  • قاعدة دائمة (مجلد مسمى) وغير منشورة أبدا : داخلية فقط.
  • إعادة المحاولة واجبة عند بدء التشغيل : فـ depends_on يرتب، وإعادة المحاولة تنتظر حقا.
  • يثبت DOWN/UP الديمومة : فيجب أن ينجو الجدول من إعادة التشغيل.

تحدي 48 ساعة — طبّق غدا

ركبوا واجهة + قاعدة مربوطتين بالأسماء مع إعادة محاولة، وأثبتوا الديمومة بـ down/up أمام زميل.

قائمة الخروج من القسم
  • خدماتي تتصل بالأسماء عبر متغيرات.
  • قاعدتي دائمة وغير منشورة.
  • واجهتي تعيد محاولة اتصالها عند بدء التشغيل.
تقييم ذاتي 1-5 : هل ما زالت IP ثابتة تتسكع في كودي أو كومبوز خاصتي ؟هل ينجو ستاكي من down/up دون فقدان ؟

أسئلة شائعة

تتحطم الواجهة عند بدء التشغيل الأول ثم تنجح : طبيعي ؟

نعم، إذا لم تُعِد المحاولة : فتبدأ القاعدة أبطأ من الواجهة (و depends_on يرتب دون انتظار). أضيفوا حلقة إعادة محاولة مع مهلة عند بدء تشغيل الواجهة — فيختفي التحطم. وتكملة المستوى 4 : فحص صحة جهة القاعدة + شرط بدء التشغيل جهة الواجهة للجدولة النظيفة.

أين نضع كلمة السر والرابط حقا ؟

في التطوير : .env غير مُلتزَم به بجانب كومبوز (موثق بـ .env.example وهمي). وفي الإنتاج : سر مُدار (ملف مركب، أو CI، أو منسق). ولا مكشوف أبدا في كومبوز المُلتزَم به، ولا في الصورة. وتبقى القاعدة غير منشورة : فلا يعبر السر إلا الشبكة الداخلية لكومبوز.

تحقّق من فهمك

يجب أن تصل الواجهة postgres على شبكة كومبوز. أي رابط سليم ؟

موارد إضافية (اختياري، للتعمّق أكثر)

اختبار المستوى 3أسئلة جديدة وأصعب للتحقق من مكتسباتك