Docker Compose خطوة بخطوة

إذا كنت قد قرأت من قبل عن مفهوم Docker Compose وفهمت لماذا نحتاج إلى تشغيل عدّة حاويات معًا، فهذا المقال هو الخطوة العملية التالية. هنا لن نتحدّث كثيرًا عن النظريات، بل سنبني تطبيقًا حقيقيًا متعدّد الخدمات خطوة بخطوة، ونتعلّم الأوامر التي ستستخدمها يوميًا في عملك. الهدف أن تخرج من هنا قادرًا على كتابة ملف docker-compose.yml من الصفر وتشغيل بيئة كاملة بأمر واحد.

التحقّق من تثبيت Docker Compose

منذ الإصدار v2 أصبح Compose جزءًا من Docker نفسه على شكل plugin، ولم يعد أداة منفصلة باسم docker-compose (بشرطة). إذا كان لديك إصدار حديث من Docker Engine أو Docker Desktop فالـ plugin مُثبّت تلقائيًا. تحقّق منه بهذا الأمر:

docker compose version

يجب أن ترى مخرجات مثل Docker Compose version v2.x.x. لاحظ المسافة بين docker وcompose بدل الشرطة القديمة. إذا لم يكن مُثبّتًا على توزيعة لينكس، يمكنك تثبيت الحزمة الرسمية:

# على Debian/Ubuntu بعد إضافة مستودع Docker الرسمي
sudo apt-get update
sudo apt-get install docker-compose-plugin

تشريح ملف docker-compose.yml

ملف Compose مكتوب بصيغة YAML، وهي صيغة حسّاسة جدًا للمسافات البادئة (indentation). كل خدمة (service) تمثّل حاوية واحدة. لنفهم البنية الأساسية:

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
    restart: unless-stopped

هنا عرّفنا خدمة واحدة اسمها web. المفتاح image يحدّد الصورة المستخدمة، وports يربط منفذ 8080 على جهازك بمنفذ 80 داخل الحاوية (الصيغة host:container)، وrestart يضمن إعادة تشغيل الحاوية تلقائيًا إذا توقّفت. لاحظ أن المسافة البادئة دائمًا بمقدار حرفين، ولا تستخدم Tab إطلاقًا.

بناء تطبيق متعدّد الخدمات: web + db

الآن سنبني بيئة حقيقية: خدمة ويب تعتمد على قاعدة بيانات PostgreSQL. أنشئ مجلّدًا جديدًا وضع بداخله ملف docker-compose.yml بهذا المحتوى:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: appdb
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5

  web:
    image: nginx:1.27
    ports:
      - "8080:80"
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

volumes:
  db_data:

دعنا نشرح الجديد هنا. خدمة db تستخدم متغيّرات بيئة لإعداد اسم المستخدم وكلمة المرور واسم قاعدة البيانات. لاحظ ${DB_PASSWORD} الذي سيُقرأ من ملف .env سنذكره بعد قليل. المفتاح healthcheck يجعل Docker يتأكّد أن قاعدة البيانات جاهزة فعلًا للاتصال، والمفتاح depends_on مع condition: service_healthy يجبر خدمة web على الانتظار حتى تصبح db سليمة قبل أن تبدأ. هذا يحلّ مشكلة شائعة حيث يبدأ التطبيق قبل جاهزية قاعدة البيانات فيفشل الاتصال.

الشبكات الافتراضية بين الخدمات

نقطة مهمّة: لم نُعرّف أي شبكة يدويًا، ومع ذلك تستطيع خدمة web الوصول إلى خدمة db باستخدام اسمها فقط. عند تشغيل المشروع، ينشئ Compose شبكة افتراضية (bridge network) خاصّة، ويصبح اسم كل خدمة بمثابة hostname داخلها. أي أن تطبيقك يتّصل بقاعدة البيانات عبر العنوان db:5432 وليس localhost. هذا أحد أقوى أسباب استخدام Compose: الخدمات تكتشف بعضها بالاسم تلقائيًا دون الحاجة لعناوين IP ثابتة.

الـ volumes والبيانات الدائمة

لاحظ في المثال السطر db_data:/var/lib/postgresql/data وكتلة volumes في الأسفل. هذا يُسمّى named volume. بدونه، إذا حذفت الحاوية فستضيع كل بيانات قاعدة البيانات لأن نظام ملفات الحاوية مؤقّت. الـ volume يخزّن البيانات خارج الحاوية على القرص، فتبقى محفوظة حتى لو أعدت بناء الحاوية أو حذفتها. القاعدة الذهبية: أي خدمة تحفظ بيانات (قواعد بيانات، ملفات مرفوعة) يجب أن يكون لها volume.

متغيّرات البيئة وملف .env

لا تكتب كلمات المرور مباشرة داخل ملف Compose. بدلًا من ذلك، أنشئ ملفًا باسم .env في نفس المجلّد، وسيقرأه Compose تلقائيًا:

# ملف .env
DB_PASSWORD=MyStr0ngP@ss

لا تنسَ إضافة .env إلى ملف .gitignore حتى لا ترفع كلمات المرور إلى مستودع Git. يمكنك التأكّد من أن القيم قُرئت بشكل صحيح قبل التشغيل عبر:

docker compose config

هذا الأمر يطبع الإعداد النهائي بعد دمج المتغيّرات، وهو ممتاز لاكتشاف الأخطاء مبكرًا.

الأوامر اليومية التي ستحتاجها

الآن لنشغّل كل شيء. هذه هي الأوامر التي ستتعامل معها باستمرار:

# تشغيل كل الخدمات في الخلفية (detached mode)
docker compose up -d

# عرض حالة الخدمات الجارية
docker compose ps

# متابعة السجلّات الحيّة لكل الخدمات
docker compose logs -f

# متابعة سجلّ خدمة واحدة فقط
docker compose logs -f db

# الدخول إلى حاوية لتنفيذ أمر بداخلها
docker compose exec db psql -U appuser -d appdb

# إعادة بناء الصور (مفيد عند استخدام Dockerfile محلي)
docker compose build

# إيقاف وحذف الحاويات والشبكة
docker compose down

# الحذف مع حذف الـ volumes أيضًا (احذر: يمسح البيانات)
docker compose down -v

الفرق المهمّ بين up وup -d هو أن -d يشغّل الخدمات في الخلفية ويعيد لك سطر الأوامر، بينما بدونها ستبقى السجلّات تملأ الشاشة وتتوقّف الخدمات إذا أغلقت الطرفية. أمّا down فيوقف الحاويات ويحذفها مع الشبكة، لكنه يحافظ على الـ volumes افتراضيًا حتى لا تفقد بياناتك.

أخطاء شائعة

  • مشاكل الـ indentation في YAML: أكثر خطأ شيوعًا. استخدام Tab بدل المسافات، أو محاذاة خاطئة، يسبّب رسالة yaml: line X: did not find expected key. استخدم دائمًا مسافتين، وفعّل في محرّرك إظهار المسافات.
  • نسيان -d: تشغّل docker compose up ثم تغلق الطرفية فتتوقّف كل الخدمات. للبيئات التي تعمل في الخلفية استخدم -d دائمًا.
  • فقدان البيانات لعدم استخدام volume: تشتغل قاعدة البيانات بشكل ممتاز، ثم تنفّذ docker compose down وتعيد التشغيل فتجد كل البيانات اختفت. السبب أنك لم تعرّف named volume لمجلّد البيانات.
  • الاتصال بقاعدة البيانات عبر localhost: من داخل حاوية web، العنوان الصحيح هو اسم الخدمة db وليس localhost، لأن كل حاوية لها شبكتها الخاصّة.

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

الآن لديك أساس صلب لتشغيل بيئات متعدّدة الخدمات. الخطوة التالية المقترحة: استبدل صورة nginx الجاهزة بتطبيقك الخاص عبر كتابة Dockerfile واستخدام المفتاح build: . بدل image داخل الخدمة. بهذا تبني صورتك من شيفرتك مباشرة، وتصبح بيئة التطوير كاملة قابلة للإقلاع بأمر docker compose up -d واحد على أي جهاز.

اترك تعليقاً