Docker والحاويات

Docker Compose

18 دقيقة الدرس 8 من 30

Docker Compose

التطبيق في بيئة الإنتاج نادرًا ما يكون حاوية واحدة. الخدمة الويب الاعتيادية تحتاج على أقل تقدير إلى ثلاثة مكونات: واجهة برمجية خلفية، وقاعدة بيانات علائقية، وذاكرة تخزين مؤقتة — إضافةً إلى معالج مهام خلفي ووسيط رسائل وخادم وكيل عكسي في حالات أكثر تعقيدًا. تشغيل كل هذه المكونات يدويًا عبر docker run يعني الربط اليدوي للشبكات والأحجام ومتغيرات البيئة وترتيب التشغيل. Docker Compose يحل هذه المعضلة بالسماح لك بتعريف المنظومة بأكملها في ملف YAML واحد والتحكم فيها بأمر واحد.

ما الذي تفعله Compose فعليًا

تقرأ Compose ملف docker-compose.yml (أو compose.yaml — كلاهما مقبول) وتترجمه إلى سلسلة منسّقة من استدعاءات Docker API: تنشئ شبكة bridge مخصصة، وتسحب الصور أو تبنيها، وتشغّل الحاويات بترتيب التبعية، وتركّب الأحجام، وتمرر متغيرات البيئة. والأهم أنها تمنح كل خدمة اسم DNS يطابق مفتاح الخدمة في الملف، فحاوية الواجهة البرمجية تصل إلى قاعدة البيانات باستخدام الاسم db فحسب — دون الحاجة إلى إدارة عناوين IP أو بنية اكتشاف الخدمات.

Compose V2 هو المعيار الحالي. الملف الثنائي القديم docker-compose (V1) وصل إلى نهاية دعمه الرسمي. يأتي Docker Desktop وDocker Engine الحديثان مع docker compose (بمسافة، لا شرطة) كإضافة CLI. استخدم docker compose في كل العمل الجديد؛ صيغة V1 متوافقة إلى حد بعيد لكن الإضافة أسرع ومدعومة.

تشريح ملف Compose

الملف التالي يعرّف منظومة ويب ثلاثية الطبقات: واجهة Node.js برمجية، وقاعدة بيانات PostgreSQL، وذاكرة تخزين مؤقتة Redis. اقرأ كل قسم بعناية — كل حقل يقابل خيار docker run تعرفته مسبقًا.

# compose.yaml — منظومة ويب ثلاثية الطبقات services: api: build: context: . target: production # مرحلة البناء متعددة الأدوار image: myapp/api:dev ports: - "3000:3000" environment: DATABASE_URL: postgres://app:secret@db:5432/appdb REDIS_URL: redis://cache:6379 depends_on: db: condition: service_healthy # انتظر الفحص الصحي لا مجرد البدء cache: condition: service_started restart: unless-stopped networks: - backend db: image: postgres:16-alpine environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: appdb volumes: - pg_data:/var/lib/postgresql/data # حجم مسمى يبقى بعد compose down healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d appdb"] interval: 5s timeout: 3s retries: 5 networks: - backend cache: image: redis:7-alpine command: redis-server --save 60 1 --loglevel warning volumes: - redis_data:/data networks: - backend volumes: pg_data: redis_data: networks: backend: driver: bridge

الاختيارات التصميمية الرئيسية في هذا الملف:

  • depends_on مع condition: service_healthy — بدون هذا الخيار تبدأ الواجهة البرمجية فور انطلاق الحاوية وليس بعد أن تصبح PostgreSQL جاهزة فعلًا لقبول الاتصالات. هذا التنافس على التوقيت يُسبب فشل التشغيل في pipelines الـCI كل يوم.
  • الأحجام المسماة (pg_data، redis_data) بدلًا من الربط المباشر لبيانات قاعدة البيانات — تبقى بعد docker compose down وتُحذف فقط بـdocker compose down -v. لا تخزن بيانات قاعدة البيانات في bind mount أبدًا؛ يخلق ذلك تعارضات في أذونات الملفات بين مستخدم الجهاز المضيف والحاوية.
  • شبكة صريحة — عزل الخدمات على شبكة مسماة يمنعها من الوصول عشوائيًا إلى حاويات مشاريع Compose أخرى، وهو حاجز أمني حقيقي في البيئات المشتركة.
Docker Compose multi-container network topology Docker Host backend (bridge network) Host :3000 api Node.js :3000 build: . / target: production db PostgreSQL 16 :5432 cache Redis 7 :6379 DATABASE_URL REDIS_URL vol: pg_data vol: redis_data
الخدمات الثلاث تشترك في شبكة bridge الخلفية؛ أسماء DNS تطابق مفاتيح الخدمات. الأحجام المسماة تحافظ على بيانات قاعدة البيانات باستقلالية عن دورة حياة الحاوية.

الأوامر الأساسية

معظم العمل اليومي مع Compose يستخدم حفنة من الأوامر:

# تشغيل جميع الخدمات في الخلفية (detached) docker compose up -d # إعادة البناء والتشغيل بعد تغيير Dockerfile docker compose up -d --build # متابعة السجلات من جميع الخدمات docker compose logs -f # متابعة سجلات خدمة واحدة فقط docker compose logs -f api # عرض الخدمات الجارية وحالتها docker compose ps # تشغيل أمر منفرد داخل حاوية خدمة docker compose exec api sh # توسيع خدمة عديمة الحالة إلى 3 نسخ (أزل تعيين المنفذ المضيف أولًا) docker compose up -d --scale api=3 # إيقاف وحذف الحاويات والشبكات (الأحجام تبقى) docker compose down # إيقاف وحذف الحاويات والشبكات والأحجام المسماة — يمحو بيانات قاعدة البيانات docker compose down -v
docker compose down -v يحذف جميع الأحجام المسماة نهائيًا. تشغيل هذا الأمر على منظومة تحتوي قاعدة بيانات حقيقية يمحو البيانات بلا رجعة. تأكد دومًا من البيئة التي تعمل فيها قبل إضافة -v. في CI هذا مقبول؛ في بيئة التجهيز يكون كارثيًا.

ملفات البيئة واستبدال المتغيرات

ترميز الأسرار مباشرةً في compose.yaml ممارسة سيئة حتى للتطوير المحلي. تحمّل Compose تلقائيًا ملف .env من مجلد المشروع، ويمكنك الإشارة إلى متغيراته بصيغة ${VAR}:

# .env (مُدرج في gitignore) POSTGRES_PASSWORD=localdevpassword API_TAG=latest APP_PORT=3000 # compose.yaml يستخدم متغيرات .env services: api: image: myapp/api:${API_TAG} ports: - "${APP_PORT}:3000" db: environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

لبيئات متعددة يمكنك الاحتفاظ بملفات تجاوز منفصلة ودمجها عند التشغيل:

  • compose.yaml — التعريفات الأساسية، مُضافة إلى git
  • compose.override.yaml — تُدمج تلقائيًا عند وجودها؛ مثالية للإضافات المحلية مثل bind-mount للكود المصدري ومنافذ التصحيح
  • compose.ci.yaml — تُدمج صراحةً في CI بـdocker compose -f compose.yaml -f compose.ci.yaml up -d

الملفات الشخصية Profiles: خدمات شرطية

تتيح الملفات الشخصية تعريف خدمات لا تُشغَّل افتراضيًا وتُفعَّل عند الطلب فقط. هذا الحل النظيف لمشكلة "أريد منظومة المراقبة في CI فقط" أو "شغّل بذر البيانات مرةً واحدة":

services: api: build: . # بلا profile = يُشغَّل دائمًا db: image: postgres:16-alpine # بلا profile = يُشغَّل دائمًا seed: image: myapp/api:dev command: node scripts/seed.js depends_on: db: condition: service_healthy profiles: - tools # يُشغَّل فقط عند تمرير --profile tools prometheus: image: prom/prometheus:latest profiles: - monitoring # الاستخدام: # docker compose up -d # يُشغّل api + db فقط # docker compose --profile tools run seed # يُشغّل seed أيضًا # docker compose --profile monitoring up -d # يُضيف prometheus
استخدم الملفات الشخصية لواجهات إدارة قاعدة البيانات. خدمات مثل Adminer أو pgAdmin قيّمة محليًا لكن يجب ألا تعمل في CI أو الإنتاج. ضعها خلف ملف شخصي debug — تعمل عند الطلب وغير مرئية في غير ذلك. هذا النمط يقضي على فئة "تركتُ Adminer مكشوفًا" من حوادث الأمان.

منظومات التطوير المحلي: ربط مباشر لإعادة التحميل الفوري

في التطوير النشط تريد دائمًا تقريبًا تركيب كودك المصدري داخل الحاوية حتى ترى التغييرات فورًا دون إعادة البناء. استخدم compose.override.yaml لذلك كي لا يصل الربط المباشر إلى CI أو الإنتاج:

# compose.override.yaml — يُدمج تلقائيًا محليًا، غير مُلتزم (أو مُدرج في gitignore) services: api: build: target: development # استخدم مرحلة التطوير مع التبعيات التطويرية volumes: - .:/app # ربط الكود المصدري مباشرةً - /app/node_modules # حجم مجهول يمنع node_modules المضيف من الطغيان command: npm run dev # nodemon / ts-node-dev يراقب التغييرات environment: NODE_ENV: development ports: - "9229:9229" # منفذ مصحح Node.js

الحجم المجهول /app/node_modules نمط معروف: يمنع node_modules الجهاز المضيف (المبني لنظام macOS/Windows) من الطغيان على نظير الحاوية (المبني لـLinux)، وهو ما يسبب فشل الثنائيات الأصلية. نفس النمط ينطبق على __pycache__ في Python وbundle في Ruby وذاكرة الوحدات في Go.

اعتبارات بيئة الإنتاج

Compose ممتازة للتطوير المحلي والنشر الصغير النطاق (خادم واحد يُشغّل منظومة متكاملة). على نطاق الإنتاج الأوسع تتدرج الفرق إلى Kubernetes. لكن Docker Compose لا تزال مستخدمة في الإنتاج في شركات كثيرة من أجل:

  • النشر على خادم واحد عبر docker compose up -d مُدار بـpipeline CI
  • بيئات اختبار التكامل في CI — تشغيل قاعدة بيانات وذاكرة تخزين مؤقتة حقيقيتين بدل محاكاتهما
  • منظومات الأدوات الداخلية التي لا تستحق تعقيد Kubernetes

عند تشغيل Compose في CI، ثبّت دائمًا وسوم الصور بـdigests أو إصدارات محددة (لا تستخدم latest أبدًا) لتضمن قابلية الاستنساخ. استخدم --wait (docker compose up -d --wait) في CI — يُوقف التنفيذ حتى تُبلّغ جميع الخدمات ذات الفحوصات الصحية بالجاهزية قبل المرحلة التالية من الـpipeline.