لماذا تستضيف Windmill ذاتيًا على خادم VPS
يعمل Windmill باستمرار على خادمك ويعرض كل سكربت من سكربتاتك كنقطة REST مصادَق عليها برمز (token)، أو كمهمة مجدولة، أو كواجهة مولَّدة تلقائيًا. وعلى خلاف الدوال عديمة الخادم (serverless)، لا يوجد إقلاع بارد ولا حد تنفيذ يفرضه طرف ثالث. تصل سكربتات Python أو TypeScript أو Bash مباشرةً إلى قواعد البيانات والخدمات الداخلية على خادمك، دون المرور ببوابة سحابية. الواجهة الخلفية مكتوبة بلغة Rust: خوادم API عديمة الحالة وعمّال (workers) يسحبون المهام من طابور مخزَّن في PostgreSQL، ما يسمح بإضافة عمّال دون المساس ببقية المكوّنات.
يتوقف الاختيار أساسًا على طريقة عملك. إن كانت حاجتك ربط خدمات SaaS ببعضها على لوحة مرئية، يبقى n8n أكثر مباشرة؛ وللعمليات التجارية الطويلة جدًا التي تتطلب ضمان الاستئناف، صُمّم Temporal لهذا الغرض. يستهدف Windmill الفرق التي تكتب الشيفرة أصلًا وتريد جعلها قابلة للتنفيذ والجدولة والاستخدام من غير المطوّرين في دقائق. أما الترخيص، فالنواة بترخيص AGPLv3 وصورة Community Edition مجانية، مع حصص وميزات محجوزة لإصدار Enterprise؛ وإعادة بيعها أو تقديمها كخدمة مُدارة لعملائك تتطلب ترخيصًا تجاريًا.
فوائد ملموسة للاستضافة الذاتية
- لا فوترة عند كل تنفيذ: آلاف المهام يوميًا دون فاتورة حوسبة سحابية.
- وصول مباشر إلى قواعد البيانات والخدمات الداخلية على الخادم، دون نفق.
- أسرار ومفاتيح API مخزّنة مشفّرة داخل Windmill ولا تغادر خادمك أبدًا.
- نماذج إدخال ونقاط REST تُولَّد تلقائيًا لكل سكربت انطلاقًا من توقيع دالته
main. - عمّال إضافيون يمكن إضافتهم أثناء التشغيل لاستيعاب ذروات الحمل.
- عدة مساحات عمل (workspaces) معزولة على نسخة واحدة: واحدة لكل فريق أو مشروع أو بيئة.
- إدارة الإصدارات: يُحتفظ بكل نشر للسكربت، ويدفع Git Sync مساحة العمل إلى مستودع Git (مضمَّن في Community Edition لمساحات العمل التي لا يتجاوز مستخدموها اثنين).
المتطلبات التقنية
يتطلب Windmill معالجَين vCPU على الأقل و4 غيغابايت من RAM. تشغّل الحزمة الرسمية PostgreSQL 18، والخادم، وثلاثة عمّال عامّين بحد ذاكرة 2 غيغابايت لكل منهم، وعاملًا أصيلًا (native) للمهام الخفيفة، والخدمة windmill_extra (الإكمال التلقائي عبر LSP ومصحّح أخطاء المحرر)، وCaddy في الواجهة: لذا قلّل عدد العمّال على خادم بسعة 4 غيغابايت. احسب 20 غيغابايت من القرص لقاعدة البيانات والسجلّات وذاكرة التخزين المؤقت لاعتماديات العمّال، التي تكبر مع كل حزمة pip أو npm مستخدمة. يكفي Ubuntu 22.04/24.04 أو Debian 12 مع Docker Engine وإضافة Docker Compose v2، إضافةً إلى اسم نطاق يشير إلى الخادم من أجل شهادة TLS. لا حاجة إلى GPU. إذا كنت تخطط لتشغيل عمّال Python ثقيلة (تعلّم آلي، أو pandas على مجموعات بيانات كبيرة)، فاختر 4 vCPU و8 غيغابايت من RAM على الأقل.
حجّم الخادم بحسب الاستخدام
- قاعدة الناشر: عامل واحد تقريبًا لكل vCPU، ومن 1 إلى 2 غيغابايت من RAM لكل عامل. المهمة التي تتجاوز حد ذاكرة عاملها (2048 ميغابايت افتراضيًا) تُقتل برمز الخروج 137.
- استخدام فردي أو تجربة: 2 vCPU و4 غيغابايت من RAM و20 غيغابايت من القرص، مع عامل عام واحد إضافةً إلى العامل الأصيل. يكفي لسكربتات مجدولة وبضع نقاط webhook.
- فريق صغير: 4 vCPU و8 غيغابايت من RAM و40 غيغابايت من القرص. يتسع المضيف نفسه للعمّال الثلاثة في الملف الرسمي ولـ PostgreSQL دون إشباع الذاكرة.
- عدة فرق أو مهام Python ثقيلة: 8 vCPU و16 غيغابايت من RAM و80 غيغابايت من القرص لستة إلى ثمانية عمّال. في هذه المرحلة، تبسّط قاعدة PostgreSQL منفصلة النسخ الاحتياطي وترقية الإصدارات.
انشر Windmill باستخدام Docker Compose
نزّل الملفات الرسمية
اتصل بخادمك عبر SSH ونزّل الملفات المرجعية الثلاثة المنشورة في مستودع Windmill: ملف
docker-compose.yml، وملفCaddyfileالخاص بالوكيل العكسي، وملف.env:mkdir -p /opt/windmill && cd /opt/windmill curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml -o docker-compose.yml curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/main/Caddyfile -o Caddyfile curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/main/.env -o .envلا حاجة إلى استنساخ المستودع كاملًا. تتبع هذه الملفات الفرع
main: أعد قراءتها قبل كل تحديث، لأن الحزمة تتطور (الانتقال إلى PostgreSQL 18، والخدمةwindmill_extra…).املأ ملف `.env`
لا يحمل ملف
.envإلا متغيرين. استبدل كلمة مرور PostgreSQL الافتراضية، وثبّت الصورة على إصدار منشور بدلًا منmain(الإصدار 1.864.1 بتاريخ 06/11/2026، تحقّق منه في صفحة الإصدارات على GitHub):DATABASE_URL=postgres://postgres:MOT_DE_PASSE_FORT@db/windmill?sslmode=disable WM_IMAGE=ghcr.io/windmill-labs/windmill:1.864.1انسخ كلمة المرور نفسها إلى
POSTGRES_PASSWORDتحت الخدمةdbفي ملفdocker-compose.yml. المضيفdbهو اسم خدمة PostgreSQL داخل الحزمة: لا تستبدله بـlocalhost. لاستخدام قاعدة بيانات موجودة، وجّهDATABASE_URLإليها واضبط الخدمةdbعلىreplicas: 0.شغّل الحزمة
شغّل المجموعة كاملة وتابع إقلاع الخادم:
docker compose up -d docker compose ps docker compose logs -f windmill_serverيجب أن يعرض
docker compose psالخدماتdbوwindmill_serverوثلاث نسخ منwindmill_workerوwindmill_worker_nativeوwindmill_extraوcaddy؛ ويبقى مفهرس البحث النصي الكامل (windmill_indexer) بلا نسخ، لأنه ميزة من إصدار Enterprise. ينتظر الخادم أن تجتاز PostgreSQL فحص الصحة، ثم يطبّق بنفسه ترحيلات المخطط. يستمع على المنفذ 8000 داخل شبكة Docker دون أن يُنشر على المضيف: Caddy هو من يستقبل حركة المرور.هيّئ الوكيل العكسي وTLS
الأبسط أن تترك Caddy المرفق يحصل على شهادة Let's Encrypt. في الخدمة
caddyداخل ملفdocker-compose.yml، ألغِ تعليق المنفذ 443 واستبدلBASE_URL=":80"باسم نطاقك، ثم أعد تشغيلdocker compose up -d:caddy: ports: - 80:80 - 25:25 - 443:443 environment: - BASE_URL=windmill.example.comأبقِ المنفذ 80 مفتوحًا لعملية التحقق. إن كان nginx يخدم مواقع أخرى على الخادم أصلًا، فاحذف الخدمة
caddy، وانشر127.0.0.1:8000:8000علىwindmill_serverو127.0.0.1:3000:3000علىwindmill_extra، ثم أعد إنتاج توجيهCaddyfile: تذهب المسارات/ws/و/ws_mp/و/ws_debug/إلىwindmill_extra، وكل ما عداها إلى الخادم.location ~ ^/ws(_mp|_debug)?/ { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } location / { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }الترويستان
UpgradeوConnectionضروريتان: يستخدم المحرر WebSockets للإكمال التلقائي وتصحيح الأخطاء. تحصل بعد ذلك على الشهادة عبرcertbot --nginx -d windmill.example.com.سجّل الدخول وأمّن النسخة
افتح
https://windmill.example.comوسجّل الدخول بحساب المشرف الأعلى الافتراضي[email protected]/changeme. يقترح معالج التشغيل الأول استبداله بحسابك الخاص: افعل ذلك، لأنك إن تخطّيت هذه الخطوة تبقى بيانات الدخول الافتراضية فعّالة، وهي الثغرة الأكثر شيوعًا في نسخ Windmill المعروضة علنًا. بعد ذلك، اضبط في Instance settings قيمة Base URL على العنوان العام بصيغةhttps://: يستخدمها Windmill لبناء عناوين webhook وروابط العودة في الـ flows. أنشئ أخيرًا أول مساحة عمل بمعرّف قصير (prodأوdata-team).اضبط عدد العمّال
يشغّل الملف الرسمي ثلاث نسخ من
windmill_worker، لكل منها حد 2048 ميغابايت. على خادم بسعة 4 غيغابايت، اخفض العدد إلى واحد أو اثنين؛ وعلى جهاز أكبر، ارفعه إلى عامل واحد لكل vCPU:docker compose up -d --scale windmill_worker=2 docker compose ps windmill_workerلجعل الإعداد دائمًا، عدّل
deploy.replicasالخاص بالخدمة في ملفdocker-compose.yml. لا يحتاج الخادم إلى إعادة تشغيل: يسجّل العمّال أنفسهم ويظهرون في صفحة Workers من الواجهة مع مجموعتهم (defaultأوnative).حدّث Windmill
غيّر الوسم في
WM_IMAGEبعد قراءة ملاحظات الإصدار، ثم:docker compose pull docker compose up -d docker image pruneيُنزّل
pullالصور الجديدة بينما لا تزال الحاويات القديمة تعمل؛ وعند إعادة التشغيل يرحّل الخادم قاعدة البيانات بنفسه، ويُنهي العمّال مهامهم الجارية ضمن مهلة الإيقاف في Docker (30 ثانية). خذ نسخة احتياطية من القاعدة قبل ذلك مباشرةً، ولا تشغّلdocker image prune -aإلا إذا كان الخادم لا يستضيف شيئًا آخر: فهذا الأمر يحذف أيضًا صور المشاريع الأخرى.
التهيئة بعد التثبيت
بعد أن تصبح النسخة متاحة، تحدد ستة إعدادات طريقة عمل سكربتاتك في الإنتاج. تُضبط جميعها من الواجهة، مساحة عمل تلو الأخرى.
الإعدادات الأساسية
- مساحات العمل: تعزل مساحة العمل السكربتات والـ flows والأسرار والأعضاء. أنشئ واحدة لكل فريق أو بيئة بدلًا من خلط كل شيء، واحجز دور المشرف الأعلى (superadmin) لإدارة النسخة.
- المتغيرات والأسرار: تُشفَّر كل المتغيرات بمفتاح متماثل خاص بمساحة العمل؛ والمتغيرات الموسومة كأسرار لا تعود تظهر في الواجهة بعد إدخالها. هنا مكان رموز API وكلمات المرور، لا في شيفرة السكربت أبدًا.
- الموارد: كائنات JSON ذات أنواع محددة (اتصال PostgreSQL، حساب SMTP، بيانات اعتماد OAuth). المعامل من نوع
postgresqlفي سكربت ما لا يقترح عندها في النموذج المولَّد إلا الموارد من هذا النوع. - الجدولة: يقبل كل سكربت أو flow جدولة واحدة أو أكثر بصيغة cron ذات الحقول الخمسة، مع حقل اختياري للثواني في البداية. معالج أخطاء الجدولة ميزة من إصدار Enterprise؛ أما في Community Edition فراقب التنفيذات الفاشلة من قائمة التشغيلات.
- Webhooks: يُستدعى السكربت بشكل غير متزامن عبر
/api/w/<workspace>/jobs/run/p/<path>(الرد: معرّف المهمة) أو بشكل متزامن عبر/api/w/<workspace>/jobs/run_wait_result/p/<path>(الرد: النتيجة). صادِق بالترويسةAuthorization: Bearer <token>بدلًا من?token=الذي ينتهي في سجلّات الوصول. - Git Sync: من إعدادات مساحة العمل، اربط مستودعًا لدفع السكربتات والـ flows والموارد إليه عند كل نشر. مجاني في Community Edition حتى مستخدمَين لكل مساحة عمل؛ أما النشر التلقائي انطلاقًا من Git فمحجوز لإصدار Enterprise.
مثال: سكربت Python من البداية إلى النهاية
يستعلم هذا السكربت واجهة API، ويحتفظ بالعناصر النشطة، ويدرجها في PostgreSQL عبر مورد من Windmill. أنشئ سكربت Python والصق الشيفرة وشغّله من المحرر:
# requirements:
# httpx
# psycopg2-binary
from typing import TypedDict
import httpx
import psycopg2
class postgresql(TypedDict):
host: str
port: int
user: str
dbname: str
sslmode: str
password: str
def main(api_url: str, db: postgresql) -> dict:
resp = httpx.get(api_url, timeout=10)
resp.raise_for_status()
rows = [r for r in resp.json() if r.get("active")]
conn = psycopg2.connect(
host=db["host"], port=db["port"], user=db["user"],
password=db["password"], dbname=db["dbname"], sslmode=db["sslmode"],
)
with conn, conn.cursor() as cur:
cur.executemany(
"INSERT INTO items (id, name) VALUES (%(id)s, %(name)s) ON CONFLICT DO NOTHING",
rows,
)
conn.close()
return {"candidates": len(rows), "source": api_url}تحلّ تعليقات # requirements: محل الاستنتاج التلقائي للاعتماديات من أوامر الاستيراد: وهذا مفيد هنا، لأن الاستيراد psycopg2 يُنشر تحت الحزمة psycopg2-binary. يحلّ Windmill هذه الحزم، ويكتب ملف قفل (lockfile)، ويخزّنها مؤقتًا على العامل، فتعمل النسخة نفسها من السكربت دائمًا بالاعتماديات نفسها. أما الصنف postgresql، المسمّى باسم نوع المورد، فيُظهر في النموذج أداة لاختيار الموارد. بعد نشره تحت المسار f/etl/import_items، يمكن استدعاء السكربت من أي أداة:
curl -X POST "https://windmill.example.com/api/w/prod/jobs/run_wait_result/p/f/etl/import_items" \
-H "Authorization: Bearer $WM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"api_url": "https://api.example.com/items"}'تقوية النسخة
لا تنشر أبدًا المنفذين 8000 و3000 على الواجهة العامة: يجب ألا يصل إليهما إلا الوكيل العكسي محليًا. خذ نسخة احتياطية من القاعدة بالأمر docker compose exec -T db pg_dumpall -U postgres | gzip > /backups/windmill-$(date +%F).sql.gz: يجب أن يغطي التفريغ العنقود (cluster) كاملًا لا قاعدة windmill وحدها، لأن Windmill ينشئ قواعد بيانات ملحقة ويربط سياسات الأمان الخاصة به بأدوار على مستوى العنقود. يحتوي هذا التفريغ على أسرارك المشفّرة: خزّنه خارج الخادم واحمِه كما تحمي سرًّا. لا تربط مقبس Docker داخل العمّال إلا إذا كنت تثق بكل مستخدمي النسخة، لأنه يمنح السكربتات السيطرة على المضيف. وأخيرًا، أبقِ REQUIRE_SIGNED_DEBUG_REQUESTS=true على windmill_extra: تعطيله على نسخة يمكن الوصول إليها من الإنترنت يعرض مصحّح أخطاء قادرًا على تنفيذ الشيفرة دون مصادقة.
استكشاف الأعطال: أخطاء النشر الأول
تأتي معظم الأعطال من قاعدة البيانات، أو ذاكرة العمّال، أو الوكيل العكسي. إليك الأعراض وطرق إصلاحها:
الأعراض وطرق الإصلاح
- تتوقف الحاوية
dbفور التحديث: أُنشئ الحجم بواسطة PostgreSQL 16 وانتقلت الصورة إلى الإصدار 18 الذي يرفض قراءته. اتبع إجراء الترحيل الموجود في التوثيق (تفريغ كامل ثم استعادة)، أو ثبّتpostgres:16مع نقطة الربط القديمة: يبقى الإصدار 16 مدعومًا حتى نوفمبر 2028. - مهمة متوقفة بالرمز 137: تجاوزت حد ذاكرة عاملها. ارفع
resources.limits.memoryفي الخدمةwindmill_worker، أو قلّل حجم البيانات التي يحمّلها السكربت في الذاكرة. - خادم أو عمّال عاجزون عن الوصول إلى القاعدة: يشير
DATABASE_URLإلىlocalhostبدلًا من الخدمةdb، أو تختلف كلمة المرور عنPOSTGRES_PASSWORD. أصلح ملف.envثم نفّذdocker compose up -dلإعادة إنشاء الحاويات. ModuleNotFoundErrorأو فشل تثبيت الاعتماديات: ينزّل العامل الحزم عند التنفيذ الأول ويجب أن يصل إلىpypi.orgوfiles.pythonhosted.orgعبر HTTPS صادر. خلف وكيل مؤسسي، يضبط المتغيرانPY_TRUSTED_HOSTوPY_INDEX_CERTالثقة؛ ويُعلَن الفهرس الخاص في Instance settings (إصدار Enterprise).- محرر بلا إكمال تلقائي ومصحّح أخطاء صامت: لا يوجّه الوكيل العكسي المسارات
/ws/و/ws_mp/و/ws_debug/إلىwindmill_extra، أو يحذف الترويستينUpgradeوConnection. اعتمد التوجيه الوارد في ملفCaddyfileالرسمي.
Windmill أم n8n أم Temporal
مرّر الجدول أفقيًا
| المعيار | Windmill | n8n | Temporal |
|---|---|---|---|
| كتابة سير العمل | سكربتات Python وTypeScript وGo وBash وSQL… تُجمَّع في flows | لوحة عُقد؛ وعقدة Code بلغة JavaScript أو Python | شيفرة تطبيقية عبر SDK (Go وJava وPython وTypeScript و.NET وPHP وRuby وRust) |
| الترخيص | AGPLv3 للنواة؛ Community Edition مجانية مع حصص، وميزات Enterprise بترخيص | Sustainable Use License (fair-code)، وملفات `.ee` بترخيص تجاري | MIT للخادم |
| الواجهة | محرر ويب، ونماذج وتطبيقات داخلية مولَّدة | محرر مرئي للعُقد | واجهة ويب للمراقبة؛ تُكتب الشيفرة خارج الأداة |
| لمن | فرق تكتب الشيفرة وتريد عرضها بسرعة | تكاملات مرئية بين خدمات SaaS | عمليات طويلة بمتطلبات متانة عالية |
نشرها بنقرة واحدة من Marketplace ServOrbit. إن كنت تفضّل تجاوز الإعداد اليدوي، يوفّر Marketplace من ServOrbit Windmill تلقائيًا — الخادم وعاملان وPostgreSQL 16 ووكيل nginx عكسي وشهادة Let's Encrypt — في بضع دقائق. يعرض قسم «الوصول الأول» في منطقة عملائك الرابطَ والبيانات المولَّدة. تتيح لك بطاقة Windmill، المتاحة من الزر في نهاية هذا المقال، البدء فورًا.