كتابة أول ملف docker-compose.yml خطوة بخطوة
كتابة أول ملف docker-compose.yml خطوة بخطوة
عندما ينتقل المشروع من حاوية واحدة إلى عدة خدمات مترابطة، يصبح التشغيل اليدوي عبر أوامر docker run مرهقاً ومعرضاً للأخطاء. هنا تظهر قيمة Docker Compose كطبقة توصيف declarative تسمح لك بتجميع التطبيق كاملاً داخل ملف واحد مفهوم وقابل للتكرار.
إذا كنت قد قرأت سابقاً ما هو Docker Compose؟ ولماذا نحتاجه لتشغيل المشاريع المعقدة؟ فهذه المقالة تمثل التطبيق العملي المباشر. سنبني ملف docker-compose.yml بطريقة هندسية، مع فهم كل سطر بدلاً من النسخ الأعمى.
هذا الأسلوب مهم جداً في بيئات DevOps لأنه يسهّل دمج بيئة التشغيل ضمن أنابيب CI/CD، ويجعل إعداد بيئات الاختبار والإنتاج أكثر اتساقاً. ولمن يريد الخلفية الأشمل، يمكن الرجوع إلى ما هو DevOps؟ ولماذا تدفع الشركات ثروات لمهندسي الأتمتة السحابية؟.
متى تحتاج إلى ملف docker-compose.yml؟
تحتاج هذا الملف عندما يصبح التطبيق مكوّناً من أكثر من خدمة، مثل واجهة ويب، قاعدة بيانات، وكاش. بدلاً من تشغيل كل خدمة يدوياً مع شبكات ومنافذ ومتغيرات منفصلة، تصف كل شيء داخل ملف YAML واحد.
هذا يقلل مشكلة اختلاف البيئات بشكل جذري، وهي نفس الفكرة التي ناقشناها في مشكلة “الكود يعمل على جهازي فقط” وكيف يحلها Docker نهائياً؟. كما أن فهمك لأوامر Docker الأساسية سيساعدك، ويمكن مراجعة أوامر Docker الأساسية للتحكم: تشغيل، إيقاف، فحص، وحذف الحاويات.
المتطلبات قبل البدء
- تثبيت
Docker EngineوميزةDocker Compose. - فهم أساسي للصور
ImagesوالحاوياتContainers. - وجود مجلد مشروع يحتوي تطبيقاً أو خدمة ويب جاهزة للتغليف.
إذا لم تكن جهزت البيئة بعد، فابدأ من تثبيت Docker وإعداد بيئة العمل على Linux و Windows. وإذا لم تفهم بعد آلية الصور، فاقرأ فهم صور دوكر (Docker Images) وكيفية سحبها وإدارتها.
هيكل المثال الذي سنبنيه
سننشئ مثالاً بسيطاً لكنه واقعي: خدمة ويب Nginx أمامية، وخدمة تطبيق، مع قاعدة بيانات Postgres. بهذا الشكل ترى فعلياً معنى الخدمات المتعددة داخل نفس المشروع.
ولو كنت قد أنشأت صورة تطبيقك مسبقاً عبر Dockerfile، فمقال كتابة أول Dockerfile: تحويل سكربت Python إلى صورة (Image) معزولة أو تحويل سيرفر Node.js متكامل إلى حاوية Docker قابلة للنقل سيكونان أساساً ممتازاً قبل ربط الخدمات معاً.
أول نسخة عملية من الملف
أنشئ داخل جذر المشروع ملفاً باسم docker-compose.yml، ثم أضف التكوين التالي:
services:
app:
build: .
container_name: myapp
ports:
- "8000:8000"
environment:
APP_ENV: development
DB_HOST: db
DB_PORT: 5432
DB_NAME: appdb
DB_USER: appuser
DB_PASSWORD: secret123
depends_on:
- db
db:
image: postgres:15
container_name: mydb
restart: unless-stopped
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: secret123
ports:
- "5432:5432"
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
شرح الملف سطراً بسطر
القسم services
هذا القسم هو قلب الملف. كل عنصر داخله يمثل خدمة مستقلة داخل المشروع. الخدمة قد تكون تطبيقاً، قاعدة بيانات، وكيل عكسي Reverse Proxy، أو أداة مساعدة.
الخدمة app
build: .تعني أن الصورة ستُبنى منDockerfileالموجود في المجلد الحالي.portsتربط منفذ الجهاز المضيف بمنفذ الحاوية.environmentتمرر متغيرات البيئة للتطبيق.depends_onتضمن بدء خدمة قاعدة البيانات قبل التطبيق من ناحية الترتيب، لكنها لا تضمن الجاهزية الكاملة للخدمة.
الخدمة db
imageتستخدم صورة جاهزة منDocker Hub.restart: unless-stoppedمفيدة لتقليل التوقف غير المتوقع بعد إعادة تشغيل الخادم.volumesتمنع فقدان البيانات عند حذف الحاوية أو إعادة إنشائها.
ولفهم أهمية هذا الجزء عملياً، راجع التخزين الدائم (Docker Volumes): كيف نمنع ضياع قواعد البيانات عند توقف الحاوية؟.
لا تضع كلمات المرور الحساسة مباشرة داخل ملف
docker-compose.ymlفي المشاريع الحقيقية. استخدم ملف.envأو حلول إدارة الأسرار مثلSecrets Managerلتقليل مخاطر التسريب داخل المستودعات البرمجية.
تشغيل المشروع لأول مرة
بعد حفظ الملف، استخدم الأمر التالي لبناء الخدمات وتشغيلها:
docker compose up --build
هذا الأمر ينفذ ثلاث مهام: بناء صورة التطبيق من Dockerfile، إنشاء الشبكة الافتراضية للمشروع، ثم إطلاق جميع الخدمات مع ربطها ببعضها تلقائياً.
ولتشغيل المشروع في الخلفية استخدم:
docker compose up -d
أما لإيقاف البيئة كاملة:
docker compose down
كيف تتخاطب الخدمات داخلياً؟
من أكثر ميزات Compose جمالاً أنه ينشئ شبكة داخلية تلقائياً. هذا يعني أن التطبيق لا يحتاج إلى عنوان IP ثابت لقاعدة البيانات، بل يكفيه استخدام اسم الخدمة مثل db.
لهذا كتبنا المتغير DB_HOST=db. داخل شبكة المشروع، هذا الاسم يُحل تلقائياً عبر DNS الداخلي الذي يديره Docker.
إضافة ملف .env لتنظيف التكوين
في المشاريع الناضجة، من الأفضل فصل القيم المتغيرة عن الملف الرئيسي. أنشئ ملف .env ثم ضعه بجوار ملف Compose:
DB_NAME=appdb
DB_USER=appuser
DB_PASSWORD=secret123
APP_PORT=8000
بعدها يمكن تعديل الملف ليصبح أكثر نظافة وقابلية للصيانة:
services:
app:
build: .
ports:
- "${APP_PORT}:8000"
environment:
DB_HOST: db
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
أخطاء شائعة عند كتابة أول ملف
- الخلط بين المسافات البادئة في
YAML، لأن أي خطأ بسيط فيindentationقد يكسر الملف بالكامل. - الاعتقاد أن
depends_onيضمن جاهزية قاعدة البيانات، بينما هو يضمن ترتيب البدء فقط. - ربط قاعدة البيانات بمنفذ خارجي دون حاجة، ما يزيد سطح الهجوم الأمني.
- عدم استخدام
volumesمع قواعد البيانات، وهو خطأ يؤدي لفقدان البيانات بسهولة.
في بيئات الإنتاج، لا تنشر جميع المنافذ إلى الإنترنت بشكل مباشر. اجعل الوصول يتم عبر
Reverse ProxyأوLoad Balancer، وفعّل المراقبة والسجلات المركزية لتقليل مخاطرDowntimeوصعوبة التشخيص.
لماذا يعتبر هذا مهماً في مشاريع CI/CD؟
عندما تملك ملف docker-compose.yml واضحاً، يصبح من السهل على أدوات مثل Jenkins أو GitHub Actions تشغيل بيئة اختبار كاملة مؤقتاً، ثم حذفها بعد انتهاء الفحوصات.
هذا النمط يختصر وقت إعداد البيئات، ويزيد من موثوقية الاختبارات التكاملية، ويجعل الانتقال لاحقاً إلى منصات أكبر مثل Kubernetes أكثر سلاسة من الناحية المفاهيمية.
الخلاصة
كتابة أول ملف docker-compose.yml ليست مجرد خطوة تشغيلية، بل هي انتقال من إدارة حاويات منفردة إلى توصيف معماري منظم للتطبيق. أنت لا تكتب أوامر فقط، بل تصف البنية، العلاقات، التخزين، ومتغيرات البيئة في وثيقة تشغيل واحدة.
ابدأ بملف بسيط، ثم وسّعه تدريجياً بإضافة شبكات مخصصة، healthchecks، وملفات بيئات متعددة. وإذا أردت تطبيقاً عملياً أكثر تعقيداً، فراجع مشروع مصغر: بناء وتغليف تطبيق تفاعلي مع قاعدة بيانات داخل حاوية معزولة لتربط المفاهيم النظرية بسيناريو حقيقي قابل للتطوير.
14 comments