شروحات تعليمية Aug. 25, 2026 7 مشاهدة

نشر بلا توقّف على خادمك الخاص: دليل عملي لتفعيل Rolling Deployment في Coolify

إذا كنت معتاداً على Vercel وأعجبك النشر دون انقطاع، فيمكنك الحصول على السلوك نفسه على خادمك الخاص عبر Coolify. دليل عملي بالأكواد: كيف يعمل الـ Rolling Update، وشروطه الثلاثة، وكيف تتخلص من ٢٠ ثانية توقّف في كل نشرة.

نشر بلا توقّف على خادمك الخاص: دليل عملي لتفعيل Rolling Deployment في Coolify

المشكلة: نشرة واحدة = صفحة 503

عندما تنتقل من منصة مثل Vercel إلى خادمك الخاص، تكتشف بسرعة أن أمر docker compose up -d ليس عملية نشر حقيقية، بل عملية إيقاف وإعادة تشغيل. الحاويات القديمة تُوقَف أولاً، ثم تُبنى الصورة الجديدة، ثم تبدأ الحاويات من جديد — وخلال هذه الفترة كل زائر يرى صفحة خطأ 503.

في القياسات المنشورة، متوسط هذا الانقطاع في مشاريع Docker Compose على Coolify يتراوح حول ٢٠ إلى ٢٥ ثانية في كل نشرة. إذا كنت تنشر مرة كل أسبوع فالأمر محتمل، أما إذا كنت تنشر عدة مرات يومياً وفي أوقات العمل، فأنت تدفع الثمن من تجربة مستخدميك ومن مصداقية منتجك.

الخبر الجيد أن Coolify يملك آلية Rolling Update مدمجة تحل المشكلة بالكامل، لكنها لا تعمل تلقائياً، ولها شروط دقيقة يتعثّر فيها أغلب المستخدمين. هذا الدليل يشرحها خطوة بخطوة.

كيف يعمل الـ Rolling Update في Coolify؟

الفكرة مستعارة من Kubernetes لكن بصورة أبسط بكثير. عند بدء عملية نشر جديدة، لا يوقف Coolify الحاوية العاملة، بل يشغّل حاوية جديدة بجانبها ويتركها تُقلع بهدوء. ثم ينتظر حتى يعلن الـ Health Check أن الحاوية الجديدة جاهزة فعلاً لاستقبال الطلبات.

عند هذه اللحظة فقط يحوّل Traefik حركة المرور إلى الحاوية الجديدة، ثم يوقف القديمة بشكل تدريجي. النتيجة أن المستخدم لا يرى أي انقطاع، وإذا فشلت الحاوية الجديدة في اجتياز الفحص فلن يتم التبديل أصلاً، وتبقى النسخة القديمة تعمل — أي أنك تحصل على حماية من النشرات الفاشلة كهدية إضافية.

لاحظ أن هذا يعني أيضاً أن نسختين من تطبيقك تعملان في اللحظة نفسها لبضع ثوانٍ. هذه النقطة سنعود إليها في قسم الهجرات (Migrations) لأنها أخطر تفصيل في الموضوع كله.

الشروط الثلاثة التي لا يعمل بدونها

هذه أهم فقرة في المقال، ولو قرأتها فقط لوفّرت على نفسك ساعات من التجريب:

١. يجب ألّا يكون المشروع منشوراً كـ Docker Compose. توثيق Coolify صريح في هذا: الـ Rolling Update غير مدعوم لمشاريع Compose، لأنها تستخدم أسماء حاويات ثابتة (static container names)، ولا يمكن تشغيل حاويتين بالاسم نفسه. لذلك يجب أن يكون التطبيق من نوع Application مبنياً من Dockerfile أو من Image جاهزة.

٢. لا تربط أي منفذ بالخادم مباشرة (Host Port Mapping). إذا كان تطبيقك يحجز المنفذ 3000 على الخادم، فلن تستطيع الحاوية الجديدة حجز المنفذ نفسه بينما القديمة ما زالت تعمل، وستفشل العملية. اترك Traefik يصل إلى الحاوية عبر الشبكة الداخلية، ولا تفتح منافذ على المضيف.

٣. يجب أن يكون هناك Health Check حقيقي ومضبوط. بدون فحص صحة، ينفّذ Coolify التبديل "بشكل أعمى"، فيتحوّل زمن إقلاع تطبيقك إلى زمن انقطاع فعلي. وفي المقابل، فحص صحة عدواني أكثر من اللازم يجعل النشرة تفشل ولا يصل كودك الجديد للإنتاج أبداً.

الخطوة ١ — تجهيز الخادم وتثبيت Coolify

تحتاج إلى VPS بنظام Ubuntu LTS (نسخة 22.04 أو 24.04)، وبمواصفات متواضعة جداً: نواتان و4 جيجابايت ذاكرة تكفي لتشغيل Coolify مع عدة تطبيقات وقاعدة بيانات وRedis بأريحية. من تجربتي الشخصية، خادم بهذه المواصفات يكلّف أقل بكثير من فاتورة منصة سحابية عند أول تجاوز لحدود الخطة المجانية.

ابدأ بخادم نظيف (بدون تطبيقات سابقة عليه) لتجنّب تعارض Docker والمنافذ، ثم نفّذ:

bash
# على خادم Ubuntu LTS نظيف، بصلاحيات root
apt update && apt upgrade -y

# فتح المنافذ المطلوبة
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 8000/tcp
ufw enable

# مثبّت Coolify الرسمي (سطر واحد)
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash

يقوم السكربت تلقائياً بتثبيت Docker وDocker Compose، وإنشاء بنية المجلدات في /data/coolify/، وتشغيل Traefik كوكيل عكسي مع شهادات Let's Encrypt، ثم تشغيل لوحة تحكم Coolify على المنفذ 8000. العملية تستغرق من ٢ إلى ٥ دقائق.

تنبيه أمني مهم: افتح الرابط http://SERVER_IP:8000 وأنشئ حساب المدير فوراً بعد انتهاء التثبيت. صفحة التسجيل مفتوحة لأول من يصل إليها، وأي تأخير يعني أن شخصاً آخر قد يستولي على لوحة التحكم بالكامل.

الخادم المستخدم في هذا الشرح

نفّذت هذه التجربة على خادم VPS من Hostinger، وهو خيار مناسب جداً لهذه الحالة تحديداً: مواصفات جيدة مقابل السعر، وقوالب جاهزة تجعل تثبيت Docker وCoolify مسألة دقائق، مع لوحة تحكم بسيطة لإدارة الـ Snapshots والنسخ الاحتياطية — وهي شبكة أمان تحتاجها فعلاً وأنت تجرّب إعدادات النشر.

إن أردت تجربة الأمر بنفسك، يمكنك الحصول على خصم عبر رابط الإحالة التالي:

الخطوة ٢ — إضافة نقطة فحص الصحة إلى التطبيق

قبل أي إعداد في واجهة Coolify، يحتاج تطبيقك إلى مسار خفيف جداً يجيب بسرعة ويؤكد أن التطبيق جاهز فعلاً. القاعدة الذهبية هنا: يجب أن يعكس هذا المسار الجاهزية لا مجرد الحياة. أي لا يكفي أن يكون خادم HTTP قد بدأ، بل يجب أن يكون الاتصال بقاعدة البيانات جاهزاً والذاكرة المؤقتة متاحة.

في المقابل لا تجعل الفحص ثقيلاً: لا استعلامات معقدة ولا نداءات لخدمات خارجية، لأنه سيُنفَّذ كل بضع ثوانٍ طوال حياة الحاوية. مثال بلغة Node.js:

javascript
// server.js
import express from "express";
const app = express();

let isReady = false;

app.get("/healthz", (req, res) => {
  if (!isReady) return res.status(503).send("starting");
  return res.status(200).send("ok");
});

const server = app.listen(process.env.PORT || 3000, async () => {
  await db.connect();   // الاتصال بقاعدة البيانات
  await cache.ping();   // التأكد من Redis
  isReady = true;       // الآن فقط نعلن الجاهزية
});

// إيقاف رشيق: أنهِ الطلبات الجارية قبل الخروج
process.on("SIGTERM", () => {
  isReady = false;
  server.close(() => process.exit(0));
});

لا تتجاوز مقطع SIGTERM في الأعلى. عند إيقاف الحاوية القديمة يرسل Docker إشارة SIGTERM، وإن لم يتعامل تطبيقك معها فستُقطع الطلبات الجارية في منتصفها. الإيقاف الرشيق هو الفرق بين "صفر انقطاع" حقيقي وبين أخطاء متفرقة يشتكي منها المستخدمون ولا تظهر في سجلاتك بوضوح.

الخطوة ٣ — ضبط HEALTHCHECK داخل الـ Dockerfile

يمكن ضبط الفحص من واجهة Coolify أو من داخل الـ Dockerfile. أفضّل الـ Dockerfile لأن الإعداد يبقى داخل مستودع الكود ويُراجَع مع بقية التغييرات. أهم معامل هنا هو start-period: وهي المهلة التي يتجاهل فيها Docker نتائج الفحص الفاشلة أثناء الإقلاع.

اضبطها بحيث تساوي زمن إقلاع تطبيقك الطبيعي مضروباً في ٢ تقريباً. لو كان تطبيقك يحتاج ١٥ ثانية ليصبح جاهزاً واخترت start-period بخمس ثوانٍ، ستفشل كل نشراتك بلا سبب واضح.

dockerfile
FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .

ENV PORT=3000
EXPOSE 3000

# interval قصير ليكتشف Coolify الجاهزية بسرعة
# start-period يجب أن يغطي زمن الإقلاع الكامل
HEALTHCHECK --interval=5s --timeout=3s --start-period=40s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/healthz || exit 1

CMD ["node", "server.js"]

لاحظ استخدام 127.0.0.1 وليس اسم النطاق: الفحص يجري من داخل الحاوية نفسها، ولا يجب أن يمر عبر Traefik أو عبر الإنترنت. واستخدمنا wget لأنها متوفرة في صور Alpine؛ إن كانت صورتك مبنية على Debian فاستخدم curl -f بدلاً منها.

الخطوة ٤ — تفكيك الـ docker-compose (الحل الجذري)

هنا يقع القرار الأهم. إن كان مشروعك ملف Compose واحداً يضم كل شيء، فلن تحصل على Rolling Update مهما ضبطت من إعدادات. الحل هو فصل الخدمة التي تستقبل طلبات HTTP عن بقية المكوّنات.

البنية القديمة عادةً تبدو هكذا:

yaml
# ❌ قبل: كل شيء في stack واحد
# كل نشرة توقف قاعدة البيانات والـ worker معها بلا داعٍ
services:
  web:
    build: .
    ports:
      - "3000:3000"        # يمنع الـ rolling update
    depends_on: [db, redis]

  worker:
    build: .
    command: node worker.js

  db:
    image: postgres:16
    volumes: [pgdata:/var/lib/postgresql/data]

  redis:
    image: redis:7

volumes:
  pgdata:

البنية الجديدة توزّع هذه الخدمات على موارد مستقلة داخل Coolify: قاعدة البيانات وRedis تُضافان كـ Services جاهزة من واجهة Coolify (فهما لا يحتاجان لإعادة التشغيل مع كل نشرة أصلاً)، وخدمة الويب تُضاف كـ Application من Dockerfile — وهي وحدها التي تحصل على الـ Rolling Update، والـ worker يُضاف كمورد ثالث.

هذا الفصل له ثمن يجب أن تعرفه مسبقاً: تفقد "المصدر الواحد للحقيقة"، إذ تنتقل نصف بنية النظام من ملف داخل مستودعك إلى إعدادات داخل قاعدة بيانات Coolify. كما تتضاعف متغيّرات البيئة عبر الموارد الثلاثة، لذا استخدم Shared Variables في Coolify من اليوم الأول بدل نسخ القيم يدوياً.

ولأن الموارد لم تعد في ملف واحد، فهي لم تعد في شبكة Docker واحدة. اربطها بشبكة مشتركة، وخاطب الخدمات باسم الحاوية الذي يولّده Coolify بدلاً من db أو redis:

bash
# ✅ بعد: ثلاثة موارد مستقلة على شبكة واحدة
# 1) خدمة Postgres (من واجهة Coolify)
# 2) خدمة Redis   (من واجهة Coolify)
# 3) تطبيق الويب  (Application من Dockerfile) ← rolling update

# في متغيرات بيئة التطبيق، استخدم اسم حاوية المورد:
DATABASE_URL=postgres://user:pass@postgresql-abc123:5432/appdb
REDIS_URL=redis://redis-xyz789:6379

# تأكد أن الموارد تشترك في الشبكة نفسها:
docker network ls
docker network connect coolify my-web-app-container

نصيحة عملية: ابنِ الصورة مرة واحدة في CI وادفعها إلى Registry، ثم اجعل تطبيق الويب والـ worker يشيران إلى الوسم (tag) نفسه. بغير ذلك ستبني الصورة نفسها مرتين، وقد تنجح نشرة الويب وتفشل نشرة الـ worker فيبقى كود قديم يعمل مقابل قاعدة بيانات جديدة.

الخطوة ٥ — تفعيل Rolling Update وإضافة Retry في Traefik

داخل إعدادات التطبيق في Coolify، فعّل خيار Rolling Update من قسم الإعدادات المتقدمة، وتأكد من خلوّ قسم Ports Mappings تماماً (اترك Ports Exposes فقط على 3000 مثلاً). ثم أضف طبقة أمان أخيرة: وسيط إعادة المحاولة في Traefik.

هذه الطبقة تلتقط الجزء الأخير من عملية التبديل — أجزاء من الثانية قد يصل فيها طلب إلى حاوية في طريقها للإغلاق — فيعيد Traefik إرسال الطلب تلقائياً إلى الحاوية الجديدة بدل إرجاع خطأ للمستخدم. أضف هذه الأسطر في حقل Custom Traefik Labels الخاص بالتطبيق:

yaml
# Custom Traefik Labels داخل إعدادات التطبيق في Coolify
traefik.http.middlewares.zdt-retry.retry.attempts=5
traefik.http.middlewares.zdt-retry.retry.initialInterval=100ms

# اربط الوسيط بالـ router الذي ولّده Coolify
# (انسخ اسم الـ router من قائمة الـ labels المولّدة تلقائياً)
traefik.http.routers.ROUTER_NAME.middlewares=zdt-retry@docker

مع هذه التركيبة — فحص صحة مضبوط + إيقاف رشيق + إعادة محاولة — تصل نسبة الطلبات الفاشلة أثناء النشر إلى الصفر عملياً، حتى تحت حِمل مستمر.

الخطوة ٦ — الهجرات المتوافقة (أخطر تفصيل)

تذكّر أن النسخة القديمة والجديدة تعملان معاً لثوانٍ. هذا يعني أن أي تغيير في قاعدة البيانات يجب أن يكون مفهوماً للنسختين في آن واحد. القاعدة تُعرف بنمط "التوسيع ثم التقليص" (Expand / Contract): وزّع التغيير على نشرتين بدل واحدة.

sql
-- ❌ خطر: النسخة القديمة ستنهار فوراً لأن العمود اختفى
ALTER TABLE users RENAME COLUMN name TO full_name;

-- ✅ النشرة الأولى (توسيع): أضف العمود الجديد فقط
ALTER TABLE users ADD COLUMN full_name varchar(255);
UPDATE users SET full_name = name WHERE full_name IS NULL;
-- الكود الجديد يكتب في العمودين، والقديم ما زال يعمل

-- ✅ النشرة الثانية (تقليص): بعد اختفاء الكود القديم تماماً
ALTER TABLE users DROP COLUMN name;

وينطبق المبدأ نفسه على الحالة داخل التطبيق: يجب أن تكون الجلسات في Redis لا في ذاكرة العملية، والملفات المرفوعة في تخزين كائني (Object Storage) لا في نظام ملفات الحاوية. وإلا فستكون قد استبدلت انقطاعاً واضحاً مدته ٢٠ ثانية بأخطاء متقطعة يصعب تتبّعها — وهي أسوأ من الانقطاع نفسه.

قياس النتيجة بنفسك

لا تصدّق أي دليل (بما فيه هذا) دون قياس. شغّل هذا السكربت من جهازك المحلي قبل الضغط على زر النشر، واتركه يعمل حتى تنتهي العملية، ثم اقرأ العدّاد:

bash
#!/usr/bin/env bash
# probe.sh — يقيس الطلبات الفاشلة أثناء النشر
URL="https://app.example.com/healthz"
fail=0
total=0

while true; do
  code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 2 "$URL")
  total=$((total + 1))
  if [ "$code" != "200" ]; then
    fail=$((fail + 1))
    echo "$(date +%T)  ❌ $code   (فشل: $fail من $total)"
  fi
  sleep 0.2
done

قبل الضبط: ستشاهد سيلاً متواصلاً من أخطاء 503 لمدة ٢٠ ثانية تقريباً. بعد الضبط: يمرّ النشر دون أن يطبع السكربت سطراً واحداً. هذا هو الفرق بين "Coolify يدعم الـ Rolling Update" كعبارة صحيحة تقنياً، وبينها كحقيقة يعيشها مستخدموك.

أخطاء شائعة تُفشل الإعداد

فحص صحة يقيس الحياة لا الجاهزية: مسار يجيب 200 بمجرد إقلاع خادم HTTP، بينما الاتصال بقاعدة البيانات لم يكتمل بعد. النتيجة تبديل مبكر وأخطاء 500 بدل 503.

start-period قصير: أشهر سبب لرسالة "النشر فشل" بلا سبب مفهوم، خصوصاً مع تطبيقات Java وNext.js الثقيلة في الإقلاع.

بقاء منفذ مربوط بالمضيف: سطر واحد منسي في إعدادات Port Mappings يعطّل الميزة بالكامل وبصمت.

نسيان الـ worker: فصلت الويب وحصلت على صفر انقطاع، لكن الـ worker بقي على النسخة القديمة يعالج مهام بمخطط بيانات جديد.

تجاهل SIGTERM: الرقم في لوحة القياس يصبح صفراً، لكن الطلبات الطويلة (رفع ملف، تقرير) تُقطع في منتصفها.

خلاصة: متى يستحق هذا الجهد؟

إن كنت تنشر مرة أو مرتين أسبوعياً وفي ساعات متأخرة، فعشرون ثانية من التوقف ثمن مقبول، ويكفيك ضبط الـ Health Check ووسيط إعادة المحاولة دون تفكيك بنيتك. أما إن كنت تنشر يومياً على منتج يستخدمه الناس في أوقات العمل، فالفصل والضبط الكامل استثمار يُسترد من أول أسبوع.

الأهم أن هذه ليست ميزة تدفع مقابلها اشتراكاً شهرياً متزايداً: إنها بنية تحتية بمستوى المنصات السحابية، على خادم تملكه وتتحكم به بالكامل، بتكلفة أقل بكثير وبلا قيود على المزوّد. وهذا تحديداً ما يجعل Coolify بديلاً جاداً وليس مجرد تجربة.

جرّب الإعداد وشاركنا في التعليقات: كم كانت مدة الانقطاع لديك قبل الضبط، وكم أصبحت بعده؟

شارك المقال
شبّك

أعجبك المقال؟ اكتشف المزيد!

تصفح مكتبتنا الشاملة من الأوامر الجاهزة والمقالات المتخصصة في الذكاء الاصطناعي