تخيّل أنك تبني تطبيق ويب بسيطًا: خدمة الويب نفسها، وقاعدة بيانات لتخزين البيانات، وطبقة كاش لتسريع الاستجابات. ثلاث حاويات فقط. تبدأ بتشغيلها يدويًا عبر 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 خطوة بخطوة» الذي يأخذك من التثبيت حتى تشغيل أول مشروع كامل بيدك.

اترك تعليقاً
يجب أنت تكون مسجل الدخول لتضيف تعليقاً.