Docker Compose: من فوضى الأوامر إلى تطبيقٍ يُدار بملفٍ واحد

Docker Compose: من فوضى الأوامر إلى تطبيق يُدار بملف واحد — بورت عرب

تخيّل أنك تبني تطبيق ويب بسيطًا: خدمة الويب نفسها، وقاعدة بيانات لتخزين البيانات، وطبقة كاش لتسريع الاستجابات. ثلاث حاويات فقط. تبدأ بتشغيلها يدويًا عبر docker run، وسرعان ما تتحوّل سطور الأوامر إلى وحوش طويلة يصعب تذكّرها أو تكرارها. هنا يبرز Docker Compose كأداة تختصر كل تلك الفوضى في ملفٍ واحد منظّم. في هذا المقال نركّز على «لماذا» نستخدمه، لا على خطوات تثبيته (لذلك مقال منفصل)، ونقارن الحياة قبله وبعده بمثال حقيقي.

ألم تشغيل الحاويات يدويًا

لنرَ شكل تشغيل تطبيق من ثلاث خدمات يدويًا. أولًا الشبكة والتخزين، ثم كل حاوية بأمر طويل منفصل:

docker network create app-net

docker volume create db-data

docker run -d --name db \
  --network app-net \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=appdb \
  -v db-data:/var/lib/postgresql/data \
  postgres:16

docker run -d --name redis \
  --network app-net \
  redis:7

docker run -d --name web \
  --network app-net \
  -p 8080:80 \
  -e DATABASE_URL=postgres://postgres:secret@db:5432/appdb \
  -e REDIS_URL=redis://redis:6379 \
  nginx:1.27

هذه السطور تكشف المشكلة بوضوح. لاحظ ثلاثة أوجاع رئيسية:

  • أوامر طويلة ومتكرّرة: كل خدمة سطر ضخم مليء بالأعلام (flags). نسيان علم واحد مثل --network يكفي لكسر التطبيق كلّه.
  • ربط يدوي للشبكة والـ volumes: عليك إنشاء الشبكة والتخزين يدويًا أولًا، وتذكّر ربط كل حاوية بهما بنفسك في الترتيب الصحيح.
  • صعوبة إعادة الإنتاج: لو سألك زميل «كيف أشغّل المشروع عندي؟» سترسل له صفحة كاملة من الأوامر. أي اختلاف بسيط في النسخ أو المنافذ يجعل بيئته تختلف عن بيئتك.

كيف يحلّ Compose المشكلة

فكرة Docker Compose بسيطة وقوية: بدلًا من إصدار الأوامر تباعًا، تصف التطبيق كاملًا — خدماته وشبكاته وتخزينه — في ملف نصّي واحد اسمه docker-compose.yml. هذا الملف يصف الحالة المطلوبة بأسلوب تصريحي (Declarative)، وتتولّى الأداة ترجمته إلى أوامر فعلية. الملف يعيش داخل مستودع المشروع، فيصبح جزءًا من الكود نفسه يُراجَع ويُشارَك ويُنسَخ بدقّة.

مثال حقيقي: web + db + redis

إليك نفس التطبيق السابق — لكن هذه المرة كملف docker-compose.yml واحد متكامل:

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
    environment:
      DATABASE_URL: postgres://postgres:secret@db:5432/appdb
      REDIS_URL: redis://redis:6379
    depends_on:
      - db
      - redis
    networks:
      - app-net

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - app-net

  redis:
    image: redis:7
    networks:
      - app-net

volumes:
  db-data:

networks:
  app-net:

دعنا نشرح المقاطع الأساسية:

  • services: القسم الجذري الذي يضمّ كل خدمة (web وdb وredis). كل خدمة هي حاوية ستُشغَّل.
  • image: الصورة المستخدمة لكل خدمة مع وسم الإصدار، مثل postgres:16. تحديد الإصدار صراحةً يضمن إعادة إنتاج موثوقة.
  • ports: ربط منفذ من الجهاز المضيف بمنفذ الحاوية بصيغة "المضيف:الحاوية". هنا 8080:80 يجعل الويب متاحًا على المنفذ 8080 من جهازك.
  • environment: متغيّرات البيئة التي تُمرَّر داخل الحاوية، مثل كلمة مرور قاعدة البيانات وروابط الاتصال.
  • depends_on: يحدّد ترتيب بدء التشغيل؛ هنا تبدأ db وredis قبل web.
  • volumes: تخزين دائم يبقى حتى بعد حذف الحاوية. ربطنا db-data بمجلّد بيانات postgres كي لا تضيع البيانات.
  • networks: شبكة افتراضية تتواصل عبرها الخدمات باستخدام أسمائها. لذلك يستطيع web الوصول إلى قاعدة البيانات عبر الاسم db مباشرةً دون عناوين IP.

المقارنة: قبل وبعد

الفرق صادم. بدلًا من إنشاء الشبكة والتخزين وثلاثة أوامر docker run طويلة، يصبح تشغيل التطبيق كلّه:

docker compose up -d

هذا الأمر الواحد يقرأ الملف، وينشئ الشبكة والـ volume تلقائيًا، ويشغّل الخدمات الثلاث بالترتيب الصحيح. ولرؤية حالة الخدمات أو سجلّاتها:

docker compose ps
docker compose logs -f web

فوائد إضافية

  • إصدار واحد للبيئة: الملف يعيش في Git مع الكود. كل من يستنسخ المشروع يحصل على البيئة نفسها بالضبط، فتختفي عبارة «يعمل عندي ولا يعمل عندك».
  • سهولة المشاركة والإقلاع: بدلًا من توثيق صفحة أوامر، يكفي زميلك git clone ثم docker compose up -d ليعمل المشروع خلال ثوانٍ.
  • إيقاف وتنظيف نظيف: أمر واحد يوقف كل شيء ويحذف الحاويات والشبكة:
docker compose down

ولو أردت حذف الـ volumes أيضًا (احذر فهذا يمسح بيانات قاعدتك)، أضف العلم -v:

docker compose down -v

أخطاء شائعة

  • توقّع أن depends_on ينتظر الجاهزية: هذا أكبر سوء فهم. depends_on يضمن أن الحاوية بدأت، لا أن الخدمة بداخلها جاهزة لاستقبال الاتصالات. قد تبدأ حاوية postgres لكن قاعدة البيانات نفسها لم تكتمل تهيئتها بعد، فيفشل اتصال الويب الأول. الحل: استخدم آلية إعادة محاولة (retry) في تطبيقك، أو أضف healthcheck للخدمة مع condition: service_healthy داخل depends_on.
  • الخلط بين الإصدار v1 وv2: الإصدار القديم كان أداة منفصلة تُستدعى بشرطة docker-compose (مع شرطة)، أما الإصدار الحديث v2 فهو إضافة (plugin) مدمجة في Docker تُستدعى docker compose (بمسافة). الأوامر متشابهة لكن لا تخلط بينهما؛ اعتمد docker compose فهو المعياري والمدعوم حاليًا.
  • إهمال وسم الإصدار في الصور: كتابة postgres دون إصدار تجلب latest الذي قد يتغيّر فجأةً ويكسر التطبيق. حدّد الإصدار دائمًا مثل postgres:16.

الخاتمة والخطوة التالية

انتقلنا من فوضى أوامر docker run الطويلة والمتكرّرة إلى ملف docker-compose.yml واحد يصف التطبيق كاملًا ويُشغَّل بأمرٍ واحد. الفائدة الكبرى ليست في توفير الكتابة فحسب، بل في جعل البيئة قابلة لإعادة الإنتاج والمشاركة والمراجعة كأي جزء من الكود. الآن وقد فهمت «لماذا» نستخدم Compose ومتى، فإن الخطوة التالية هي التطبيق العملي خطوةً بخطوة: راجع مقال «Docker Compose خطوة بخطوة» الذي يأخذك من التثبيت حتى تشغيل أول مشروع كامل بيدك.

اترك تعليقاً