تطوير الويب

Docker Compose في الإنتاج: درس 2026

7 دقائق للقراءة

راجع دليل Docker الكامل.

Docker Compose في 2026: هل لا يزال ملائماً للإنتاج؟

كثير من الفرق يظن أن Docker Compose مخصّص للتطوير المحلي وأن الانتقال إلى Kubernetes ضروري بمجرّد الحديث عن الإنتاج. هذا خاطئ لـ 80% من الشركات الصغيرة والمتوسطة. إن أدرت 1 إلى 5 خدمات على 1 إلى 3 VPS، فإن Compose v2 (المدمج في ملحق docker compose، إضافة إلى ثنائي docker-compose التاريخي) كافٍ تماماً، أسرع تعلّماً، أكثر اقتصاداً للموارد، وأقل تكلفة في الصيانة. لـ Kubernetes مكان من عشرات الخدمات، autoscaling إقليمي، أو فرق فوق 8 أشخاص — ليس قبل ذلك.

يفترض هذا الدرس VPS Linux (Debian 12 أو Ubuntu 24.04 LTS) مع Docker Engine 27+ مثبَّتاً عبر المستودع الرسمي، اسم نطاق يشير إلى VPS، ومشروع Node.js أو Python أو PHP مُحوسَب مسبقاً. إن بدأت من الصفر، اختر Hetzner CX22 (4.51 يورو/شهر) — أفضل نسبة جودة/سعر على السوق الأوروبية الميسّرة من منطقة MENA بكمون 40 إلى 80 مللي ثانية.

البنية الموصى بها

# compose.yml (إنتاج)
services:
  web:
    image: registry.exemple.com/myapp:latest
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "0.5"
    environment:
      DATABASE_URL: APP_DATABASE_URL
    networks:
      - internal
      - traefik-public
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    labels:
      - traefik.enable=true
      - traefik.http.routers.web.rule=Host(exemple.com)

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - internal

volumes:
  db-data:

networks:
  internal:
  traefik-public:
    external: true

secrets:
  db_password:
    file: ./secrets/db_password.txt

الخطوة 1 — هيكلة المشروع للإنتاج

الخطأ الكلاسيكي إبقاء ملف compose.yaml واحد يخدم dev وstaging والإنتاج. المذهب الحديث يفصلهما: compose.yaml أساسي (مشترك للبيئات الثلاث) وcompose.prod.yaml يطغى عليه بمعلمات الإنتاج.

# /srv/monapp/compose.yaml
services:
  app:
    image: monapp:latest
    restart: unless-stopped
    environment:
      NODE_ENV: production
    networks:
      - web
  db:
    image: postgres:17
    restart: unless-stopped
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - web

volumes:
  db_data:

networks:
  web:

شغّل الإنتاج بـ docker compose -f compose.yaml -f compose.prod.yaml up -d. الملف الثاني يضيف الموارد، الأسرار والسجلات الملائمة للإنتاج دون تلويث dev.

الخطوة 2 — تحديد الموارد (deploy.resources)

بلا حدود، حاوية تتسرب ذاكرتها تقتل كل VPS. ضع حدوداً منهجياً:

# compose.prod.yaml
services:
  app:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          memory: 256M
  db:
    deploy:
      resources:
        limits:
          cpus: "1.5"
          memory: 1024M

على Hetzner CX22 (2 vCPU، 4 جيغا RAM)، خصّص 30 إلى 50% للتطبيق، 25% لقاعدة البيانات، واحتفظ بـ 1 جيغا حراً للنظام والسجلات وقمم الحمل. تحقّق بـ docker stats عند الشكّ — أداة قليلة الاستخدام لكنها فتّاكة في التشخيص.

الخطوة 3 — Healthchecks لإعادة تشغيل تلقائي

restart: unless-stopped وحده لا يكفي: Docker يُعيد تشغيل الحاوية فقط إن انهارت، لا إن أصبحت خاملة (deadlock، تسرّب fd). Healthcheck يحوّل خدمة zombie إلى إعادة تشغيل فعّالة:

services:
  app:
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

أضف على جانب التطبيق endpoint HTTP /health يُرجع 200 إن كان كل شيء على ما يُرام، 503 وإلا. لا تضع منطقاً ثقيلاً داخله (لا استعلام قاعدة بيانات عند كل فحص). مدمجاً مع depends_on: condition: service_healthy، تطبيقك ينتظر حتى يكون Postgres جاهزاً فعلاً قبل التشغيل — لا مزيد من connection refused عند الإقلاع.

الخطوة 4 — الأسرار ومتغيّرات البيئة الآمنة

لا تُودِع كلمات سرّ في compose.yaml أبداً. ثلاث تقنيات بترتيب أمان متصاعد.

ملف .env (أساسي لكن صحيح): أنشئ /srv/monapp/.env بصلاحية 0600، يحتوي POSTGRES_PASSWORD=longue_chaine_aleatoire. Compose يحمّله تلقائياً ويستبدل POSTGRES_PASSWORD في yaml.

Docker secrets (موصى بها في الإنتاج):

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: /srv/monapp/secrets/db_password.txt

الملف السري بصلاحية 0400 يُربط للقراءة فقط داخل الحاوية. الميزة: لا يظهر في docker inspect ولا في قائمة متغيّرات البيئة.

Vault خارجي (HashiCorp Vault، Bitwarden Secrets Manager): مُفرَط لـ 90% من الشركات. مخصّص للفرق بمتطلبات امتثال قوية.

الخطوة 5 — Reverse proxy مع Caddy أو Traefik

لإنهاء HTTPS والتوجيه نحو الحاويات الصحيحة، Caddy 2.8+ هو الحلّ الأبسط. سطر إعداد واحد لكل موقع.

# compose.prod.yaml إضافة
services:
  caddy:
    image: caddy:2.8
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
    networks:
      - web

volumes:
  caddy_data:

وفي Caddyfile:

monapp.exemple.com {
  reverse_proxy app:3000
  encode gzip zstd
}

Caddy يدير Let’s Encrypt تلقائياً، يعيد توجيه HTTP→HTTPS، ويفعّل HTTP/3 افتراضياً. للحالات الأعقد (load balancing، A/B testing)، انتقل إلى Traefik باكتشافه التلقائي عبر labels Docker.

الخطوة 6 — سجلات مركزية مع تدوير

افتراضياً، Docker يكتب السجلات في /var/lib/docker/containers ولا يُدوّرها — خدمة ثرثارة تملأ القرص في 3 أسابيع. اضبط التدوير على مستوى daemon:

// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}

أعد التحميل بـ systemctl restart docker (يؤثر على الحاويات: نفّذ في نافذة مخطّط لها). لمتابعة مركزية، صدّر إلى Loki أو Grafana Cloud (مجاني حتى 50 جيغا/شهر). stack بـ promtail + loki + grafana على نفس VPS يكلّف 200 ميغا RAM ويعطي واجهة بحث كاملة.

الخطوة 7 — نشر zero-downtime

للتحديث بلا قطع مرئي، نهجان حسب الحرجية.

الطريقة البسيطة (تكفي لـ 80% من الحالات): docker compose pull && docker compose up -d. Compose يُعيد تشغيل فقط الحاويات التي تغيّرت صورها. لخدمة web، الانقطاع 2 إلى 5 ثوانٍ — مقبول خارج ساعات الذروة.

طريقة rolling مع Caddy: أنشئ نسختين app_blue وapp_green، أشِر Caddy إلى الإصدار الجديد بعد healthcheck OK، ثم أوقف القديم. Compose لا يفعل ذلك أصلياً — استخدم سكربت shell من 30 سطراً أو انتقل إلى Docker Swarm مع --update-parallelism 1 إن أصررت على rolling حقيقي.

الخطوة 8 — نسخ احتياطي آلي

كل volumes مسمّاة تعيش في /var/lib/docker/volumes. انسخ هذا المجلد كل ليلة مع نظام نسخ Restic. لـ Postgres، أكمل بـ dump تطبيقي:

docker compose exec -T db pg_dump -U postgres monapp | gzip > /backup/db-$(date +%F).sql.gz

dump SQL أصغر (ضغط عالٍ) ويسترجع أسرع من volume ثنائي عند الهجرة إلى Postgres آخر. احتفظ بالاثنين: volume + dump.

الأمان: تحصين daemon Docker

أربعة معايير اضبطها فور دخول الإنتاج. أولاً، Rootless Docker للـ VPS المشترك بين عدة فرق. ثانياً، عطّل capabilities غير الضرورية في كل خدمة بـ cap_drop: [ALL] ثم cap_add مستهدف. ثالثاً، فعّل security_opt: [no-new-privileges:true] لحجب أي تصعيد امتيازات. أخيراً، افحص الصور بـ Trivy قبل كل نشر:

docker run --rm aquasec/trivy:latest image monapp:latest --severity HIGH,CRITICAL --exit-code 1

في CI/CD، أَفشِل الـ build إن اكتُشفت CVE حرجة. 30 ثانية مُضافة للخط تمنع نشراً ثغرة.

تحديثات OS وإعادة تشغيل مجدولة

VPS Linux غير مُصان هو VPS مُخترق في 6 أشهر. فعّل unattended-upgrades على Debian/Ubuntu لتطبيق تصحيحات الأمان تلقائياً. برمج إعادة تشغيل أسبوعية الأحد 04:00 GMT (ساعة ميتة عبر MENA) لتطبيق تحديثات kernel. مع restart: unless-stopped على كل خدماتك، تعود للاشتغال تلقائياً بعد reboot — RTO أقل من 90 ثانية.

تشخيص: أوامر مفيدة للحفظ

مشغّل Docker متمكّن يتقن عشرة أوامر للتشخيص في أقل من 5 دقائق. docker compose ps يعطي حالة كل خدمة مع healthcheck. docker compose logs -f --tail=200 app يتابع آخر السجلات في streaming. docker stats يعرض آنياً CPU، RAM، IO شبكة وقرص لكل حاوية. docker compose top يسرد العمليات داخل الحاويات. docker compose exec app sh يفتح shell لفحص نظام الملفات أو متغيّرات البيئة. أخيراً، docker system df وdocker system prune -a --volumes ينظّفان الصور القديمة — ثمين حين يبدأ /var/lib/docker بالامتلاء.

الهجرة من legacy bare metal

إن استلمت stack legacy (Apache + MySQL + cron PHP) على خادم مخصّص مُسنّ، فالهجرة إلى Docker Compose تتم تدريجياً. ابدأ بحوسبة خدمة واحدة غير حرجة (cron أو worker)، اختبر بالتوازي مع الإنتاج الموجود لأسبوعين، ثم بدّل الباقي خدمة بخدمة. هذا النهج «strangler pattern» يتجنّب الانفجارات. هجرة نمطية لوكالة رقمية تأخذ 3 إلى 5 أيام عمل ممتدّة على شهر، بلا قطع مرئي للعملاء.

Profiles Compose لبيئات متعددة

التوجيه profiles يسمح بتفعيل/تعطيل خدمات حسب السياق. عملي للاحتفاظ في ملف واحد بأدوات تُستخدم ظرفياً (Adminer لـ Postgres، MailHog لاعتراض الإيميل في staging) دون أن تشتغل دائماً في الإنتاج. شغّل بـ docker compose --profile debug up -d فقط عند الحاجة. ربح RAM وسطح هجوم معتبر على VPS متواضع.

الشبكة والعزل بين الخدمات

افتراضياً، كل خدمات Compose نفسه تشترك في الشبكة نفسها وتستطيع التحدث. للعزل، أنشئ عدة شبكات. مثال نمطي: شبكة frontend حيث يعيش Caddy والتطبيق، وشبكة backend حيث تعيش قاعدة البيانات. Caddy لا حاجة له لبلوغ Postgres مباشرة — التطبيق يصلح وسيطاً. إن اخترق Caddy، لا يحصل المهاجم على قاعدة البيانات.

للتعمّق

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité