Strapi Cloud أو الاستضافة الذاتية: ما الذي تخسره وما الذي تكسبه
Strapi Cloud مريح للبدء، لكنّه يفرض قيودًا تصبح عائقًا سريعًا في الإنتاج الفعلي. تحدّ خطّة Free من أنواع المحتوى ومن المستخدمين الإداريين؛ وتحتسب الخطط المدفوعة الفاتورة على عدد طلبات API والملفّات المرفوعة. والأهمّ من ذلك أنّ بياناتك تقيم على البنية التحتية لشركة Strapi نفسها — وهو ما يتعارض مع متطلّبات توطين البيانات لدى كثير من العملاء (RGPD صارم، وعقود قطاعية، وبيانات حسّاسة). وبالاستضافة الذاتية على خادم VPS، تستعيد الملكية الكاملة لقاعدة بيانات PostgreSQL، وحرّية اختيار التخزين (قرص محلّي أو حاوية متوافقة مع S3)، وحرّية تثبيت أيّ إضافة من إضافات المجتمع، وأحجام محتوى لم تعد مرتبطة بجدول أسعار. المقابل تشغيلي — أنت من يتولّى التحديثات والنسخ الاحتياطية والمراقبة. ويريك هذا الدليل كيفية ذلك بالضبط.
فوائد الاستضافة الذاتية لـ Strapi على خادم VPS
- ملكية البيانات — تبقى قاعدة بيانات PostgreSQL وملفّاتك المرفوعة على خادم VPS الخاص بك، تحت سيطرتك الحصرية
- لا حصص اشتراك — لم يعد عدد المُدخلات وأنواع المحتوى وحسابات الإدارة محكومًا باشتراك، بل بموارد خادم VPS الخاص بك
- إضافات المجتمع — تُثبَّت أيّ إضافة npm بحرّية، دون مصادقة من المنصّة ولا قائمة سماح
- API على نطاقك — تُكشف واجهتا REST وGraphQL على
cms.your-domain.com، دون وسيط ولا حدّ معدّل مفروض - بنية موحّدة — يتعايش Strapi وواجهة أمامية Nuxt أو Next.js على خادم VPS واحد، خلف وكيل عكسي وحيد
- توطين البيانات — ضروري للامتثال لـ RGPD ولمتطلّبات عملائك التعاقدية
- تكلفة يمكن التنبّؤ بها — للخادم VPS رسم شهري ثابت، مستقلّ عن حجم المحتوى أو حركة مرور API
- التحكّم في الترحيلات — أنت من يختار متى وكيف تطبّق تحديثات Strapi
المتطلّبات: العتاد والبرمجيات والنطاق
يستهلك Strapi موارد أكثر من معظم الأطر لأنّ بناء لوحة إدارة React يستهلك وحده أكثر من 2 جيجابايت من الذاكرة. وخادم VPS بذاكرة 1 جيجابايت سيفشل دائمًا بنفاد الذاكرة خلال مرحلة البناء — لا تختر حجمًا أقلّ ممّا يلزم. الحدّ الأدنى المستهدف: معالجان افتراضيان و4 جيجابايت من الذاكرة. وفي الإنتاج المستقرّ، بعد اكتمال البناء، يعمل التطبيق بارتياح بذاكرة تتراوح بين 512 ميجابايت و1 جيجابايت. على صعيد البرمجيات: Docker 24+ وDocker Compose الإصدار الثاني (docker compose، لا docker-compose)، وGit لجلب الكود على الخادم، وCertbot من أجل TLS. على صعيد قاعدة البيانات: يُنصح بـ PostgreSQL 16 في الإنتاج — فـ SQLite يؤدّي وظيفته في التطوير لكنّه محدود تحت الحمل المتزامن وغير متوافق مع بعض الإضافات. على صعيد الشبكة: وجّه cms.your-domain.com نحو عنوان IP الخاص بخادمك قبل البدء، وجهّز 20 جيجابايت من القرص كحدّ أدنى (node_modules، والملفّات المرفوعة، وتفريغات النسخ الاحتياطية).
تجهيز المشروع قبل لمس الخادم
إنشاء مشروع Strapi
على جهازك، أنشئ المشروع بالأمر الرسمي
npx create-strapi-app@latest my-cms --dbclient=postgres. اختر TypeScript إن كان فريقك متمكّنًا منها. هيّئ مستودع Git وادفعه إلى منصّة الكود لديك، ثمّ انسخه على خادم VPS في/srv/my-cms. وكلّ ما يلي يُجهَّز في هذا المستودع لا على الخادم: يجب ألّا يتلقّى الخادم إلّا كودًا موضوعًا تحت إدارة الإصدارات.ربط إعداد قاعدة البيانات بـ PostgreSQL
في
config/database.js— أوconfig/database.tsمع TypeScript — تتلقّى الدالة المصدَّرةenvوتُعيد كائنconnection. صرّح بـclient: 'postgres'، ثمّ بستّة مفاتيح تُقرأ من البيئة داخل الكائن الفرعيconnection:host: env('DATABASE_HOST', '127.0.0.1')وport: env.int('DATABASE_PORT', 5432)وdatabase: env('DATABASE_NAME', 'strapi')وuser: env('DATABASE_USERNAME', 'strapi')وpassword: env('DATABASE_PASSWORD', '')وssl: env.bool('DATABASE_SSL', false). والوسيط الثاني فيenv()ليس إلّا قيمة احتياطية للتطوير: لا يجوز أن ترد أيّ قيمة حقيقية في هذا الملفّ، فهو يذهب إلى Git.إنشاء أسرار الإنتاج الخمسة
يرفض Strapi البدء في الإنتاج دون
APP_KEYSوAPI_TOKEN_SALTوADMIN_JWT_SECRETوJWT_SECRETوTRANSFER_TOKEN_SALT. أنشئ كلًّا منها بـopenssl rand -base64 32؛ ويتوقّعAPP_KEYSأكثر من مفتاح، مفصولة بفواصل، فأنتج اثنين على الأقلّ. اكتبها في/srv/my-cms/.envعلى خادم VPS — لا في Git أبدًا — معDATABASE_HOSTوDATABASE_NAMEوDATABASE_USERNAMEوDATABASE_PASSWORDوNODE_ENV=productionوURL=https://cms.your-domain.com. والمفتاح الناقص يُترجَم إلى رفض للبدء، لا إلى تحذير.كتابة ملفّ Dockerfile متعدد المراحل
تكفي مرحلتان لإبقاء صورة الإنتاج خفيفة. تنطلق مرحلة
builderمنnode:20-alpine، وتثبّتWORKDIR /app، وتنسخpackage*.json، وتشغّلnpm ci، ثمّ تنسخ بقيّة الكود، وتصرّف لوحة الإدارة بـNODE_ENV=production npm run build. وتنطلق مرحلةrunnerمن الصورة الأساسية نفسها، ولا تنسخ منbuilderسوى ثلاثة أشياء: مجلّد البناء، وnode_modules، وpackage.json. اختم بـEXPOSE 1337وبتعليمةCMDتستدعيnpm run start— فهو سكربت البدء الذي يثبّته Strapi، ولا تحاول تشغيل ملفّ خادم يدويًا. وإن مات البناء بنفاد الذاكرة، ضعNODE_OPTIONS=--max-old-space-size=4096قبلnpm run build.
نشر المنظومة على خادم VPS
وصف الخدمات في ملفّ docker-compose
يصرّح ملفّ
docker-compose.ymlعن خدمتين ووحدة تخزين مسمّاة. تستخدم خدمةpostgresصورةpostgres:16-alpine، وتتلقّىPOSTGRES_DBوPOSTGRES_USERوPOSTGRES_PASSWORDفي كتلةenvironmentالخاصّة بها، وتركّب وحدة التخزينpgdataعلى/var/lib/postgresql/data، وتعمل بـrestart: unless-stopped. أمّا خدمةstrapiفتُبنى من ملفّ Dockerfile المحلّي بـbuild: .، وتقرأ متغيّراتها عبرenv_file: .env، وتصرّح بـdepends_on: postgres، وتركّب./public/uploadsعلى/app/public/uploads، وتنشر منفذها بـports: 127.0.0.1:1337:1337. وهذا الربط على حلقة الاستدعاء المحلّية هو النقطة المهمّة: يصبح Nginx نقطة الدخول العامة الوحيدة، ولا يُكشف المنفذ 1337 إلى الخارج أبدًا. وتبقى خدمةredisاختيارية، لتخزين الجلسات مؤقّتًا أو لطوابير المعالجة.بناء الصورة وتشغيل المنظومة
شغّل
docker compose build، ثمّdocker compose up -d. وتابع الإقلاع بـdocker compose logs -f strapi. يطبّق Strapi ترحيلات المخطّط عند أوّل تشغيل ما دامNODE_ENV=productionموضوعًا. احسب من دقيقتين إلى خمس دقائق: بناء لوحة إدارة React هو أبطأ خطوة بفارق كبير. وانتظر سطرStrapi started successfullyقبل المتابعة.وضع Nginx وكيلًا عكسيًا
أنشئ المضيف الافتراضي
/etc/nginx/sites-available/cms.your-domain.com. يستمع بـlisten 80علىserver_name cms.your-domain.com، ويحملclient_max_body_size 50M— وهو ضروري لرفع الوسائط — وكتلةlocation /وحيدة تنفّذproxy_pass http://127.0.0.1:1337. أضف إليه الترويسات الأربع التي يتوقّعها Strapi خلف وكيل:HostوX-Real-IPوX-Forwarded-ForوX-Forwarded-Proto، كلٌّ منها موضوعة بتوجيهproxy_set_header. فعّل المضيف الافتراضي برابط رمزي نحوsites-enabled، ثمّ تحقّق وأعد التحميل بـnginx -t && systemctl reload nginx.تفعيل HTTPS وتثبيت المتغيّر URL
احصل على الشهادة بـ
certbot --nginx -d cms.your-domain.com: يعيد Certbot كتابة المضيف الافتراضي لتحويل HTTP نحو HTTPS. وتحقّق بعد ذلك من ورودURL=https://cms.your-domain.comفي ملفّ.env، دون شرطة مائلة أخيرة. فهذا هو المتغيّر الذي يستخدمه Strapi لبناء روابط الوسائط وإعادة توجيهات لوحة الإدارة؛ ودونه تخرج الملفّات المرفوعة بعناوين خاطئة وتسيء لوحة الإدارة التصرّف خلف الوكيل. أعد تشغيل الحاوية بعد كلّ تعديل على.env: تُقرأ المتغيّرات عند الإقلاع.التحقّق من أنّ النسخة تستجيب فعلًا
افتح
https://cms.your-domain.com/adminوأنشئ أوّل حساب إداري — يطلبه Strapi عند أوّل وصول ولا يعود يطلبه. راقب بعد ذلك ثلاث نقاط: يُظهرdocker compose psالحاويتين في حالةrunning، وتستجيب الواجهة العامة على/api، وينجح إرسال ملفّ تجريبي من مكتبة الوسائط. ويشير502 Bad Gatewayفي هذه المرحلة، في الغالب الأعمّ، إلى حاويةstrapiمتوقّفة أو ما تزال في طور البناء: أعد قراءةdocker compose logs strapiقبل لمس Nginx.
إخراج الوسائط من القرص: مزوّد S3
يضع Strapi الملفّات المرفوعة افتراضيًا في public/uploads، على قرص خادم VPS. ويجعلها تركيب ./public/uploads في ملفّ docker-compose تنجو من إعادة إنشاء الحاوية، لكنّها تكبر مع مكتبة الوسائط، وتدخل في كلّ نسخة احتياطية، وتختفي مع الخادم. ويحلّ المزوّد الرسمي المشكلات الثلاث: npm install @strapi/provider-upload-aws-s3. صرّح به بعد ذلك في config/plugins.js، تحت المفتاح upload ثمّ config: provider: 'aws-s3' وكائن providerOptions يقرأ أربع قيم من البيئة — accessKeyId: env('AWS_ACCESS_KEY_ID') وsecretAccessKey: env('AWS_ACCESS_SECRET') وregion: env('AWS_REGION') وparams: { Bucket: env('AWS_BUCKET') }. وأيّ تخزين متوافق مع S3 يفي بالغرض: Scaleway Object Storage أو Wasabi أو Cloudflare R2. وثمّة مطبّان يستحقّان المعرفة قبل التحويل. فالوسائط المرفوعة سابقًا لا تنتقل وحدها — حوّل المزوّد قبل الإطلاق، أو انسخ محتوى public/uploads إلى الحاوية يدويًا. وتتغيّر نطاقات روابط الوسائط: إن كانت واجهتك الأمامية تخزّنها مؤقّتًا أو تعيد كتابتها، فتحقّق من ذلك بعد التحويل.
أتمتة النسخ الاحتياطي لقاعدة البيانات
إنشاء مجلّد الوجهة
على خادم VPS،
mkdir -p /srv/backups/strapi. أبقِ هذا المجلّد خارج مستودع Git وخارج أيّ مسار مركّب في حاوية: يجب ألّا يذهب التفريغ في صورة ولا في إصدار.تحديد الاسم الحقيقي لحاوية PostgreSQL
يعطي
docker compose psالاسم الدقيق، على صيغةmy-cms-postgres-1. وهو مشتقّ من اسم مجلّد المشروع: لا تنسخه من دليل، بل اقرأه على جهازك. وسكربت النسخ الاحتياطي الذي يستهدف حاوية غير موجودة يفشل بصمت بمجرّد وضعه في cron.كتابة أمر التفريغ
يكفي سطر واحد:
docker exec my-cms-postgres-1 pg_dump -U strapi strapi | gzip > /srv/backups/strapi/strapi-$(date +%Y%m%d).sql.gz. ينفَّذpg_dumpداخل الحاوية، ويجري الضغط والكتابة على المضيف. شغّله مرّة أولى يدويًا وتحقّق من حجم الملفّ الناتج: تفريغ بحجم بضعة بايتات يشير إلى خطأ مصادقة ابتلعته الأنبوبة.تنظيف التفريغات القديمة
أضف بعده
find /srv/backups/strapi -name '*.sql.gz' -mtime +7 -delete. ودون هذا السطر يمتلئ القرص في أسابيع قليلة: وهو أكثر أعطال النسخ الاحتياطي اليومي شيوعًا — تسقط القاعدة لأنّ النسخ الاحتياطي أشبع وحدة التخزين.الجدولة، ثمّ إخراج التفريغات من الخادم
ضع الأمرين في سكربت
/etc/cron.daily/strapi-backup، مع#!/bin/bashفي السطر الأوّل ومعchmod +xلجعله قابلًا للتنفيذ. أضف إليهrsyncنحو تخزين خارجي، أو إرسالًا نحو حاوية S3 نفسها المستعملة للوسائط. والنسخة الاحتياطية التي تبقى على الجهاز الذي تحميه لا تحمي من شيء؛ فاختبر استعادة كاملة مرّة واحدة على الأقلّ قبل الاعتماد عليها.
تحديث Strapi دون كسر الترحيلات
يُدار التحديث من المستودع لا على الخادم: غيّر الإصدار في package.json، وادفع، ثمّ على خادم VPS تابع بـ git pull وdocker compose build && docker compose up -d. يكتشف Strapi ترحيلات المخطّط ويطبّقها عند الإقلاع ما دام NODE_ENV=production موضوعًا؛ أكّد ذلك بـ docker compose logs strapi | grep -i migrat. وثلاثة احتياطات تستحقّ العناء. خذ pg_dump قبيل رفع الإصدار مباشرةً، لا نسخة الليلة السابقة فحسب — فالترحيل الفاشل يُصلَح باستعادة، لا بمحاولة ثانية. واقرأ ملاحظات إصدار الإضافات التي ثبّتها: فترقية كبرى لـ Strapi تكسر إضافة مجتمعية أكثر ممّا تكسر النواة نفسها. وأخيرًا، إن فشل ترحيل فلا تعد تشغيل المنظومة في حلقة: كلّ إعادة تشغيل تعيد تنفيذ الترحيل نفسه على قاعدة عُدّل نصفها. أوقف الحاويات، واقرأ السجلّ كاملًا، واستعد التفريغ عند اللزوم، ثمّ صحّح.
استكشاف الأخطاء: أكثر الأعطال شيوعًا
نفاد الذاكرة خلال البناء (Killed أو JavaScript heap out of memory): بناء لوحة إدارة React يتخطّى الذاكرة المتاحة. وثمّة علاجان — إضافة NODE_OPTIONS=--max-old-space-size=4096 في مرحلة builder من ملفّ Dockerfile، أو زيادة مساحة التبديل في خادم VPS مؤقّتًا بـ fallocate -l 2G /swapfile وchmod 600 /swapfile وmkswap /swapfile ثمّ swapon /swapfile. وإن ظلّ الخادم عالقًا، فابنِ الصورة على جهاز أقوى وادفعها إلى سجلّ صور.
Cannot find module @strapi/plugin-*: لا تشارك أبدًا مجلّد node_modules بين بيئة تطوير محلّية وصورة الإنتاج عبر وحدة تخزين Docker. فـ node_modules المحلّي مصرَّف لنظام تشغيلك، لا لنظام Alpine Linux الخاصّ بالحاوية. أزل أيّ وحدة تخزين node_modules من ملفّ docker-compose، ودع npm ci الخاصّ بملفّ Dockerfile يتولّى الأمر.
URL mismatch في لوحة الإدارة، أو وسائط بمسار نسبي: يجب أن يطابق متغيّر URL في ملفّ .env عنوان Strapi العام على HTTPS تمامًا، دون شرطة مائلة أخيرة. وأيّ تباين يكسر روابط الوسائط المرفوعة ويسبّب أخطاء CORS في لوحة الإدارة.
413 Request Entity Too Large: توجيه client_max_body_size في Nginx منخفض أكثر ممّا يجب. ارفعه إلى 50M كحدّ أدنى، أو إلى 100M إن كنت ترفع مقاطع فيديو، ثمّ أعد تحميل Nginx.
password authentication failed for user عند الإقلاع: تغيّرت كلمة المرور في ملفّ .env بعد إنشاء وحدة التخزين pgdata. ولا يعيد PostgreSQL قراءة كلمة مروره إلّا عند تهيئة وحدة التخزين؛ فواءم ملفّ .env مع كلمة المرور القائمة، أو انطلق من وحدة تخزين جديدة بعد أخذ نسخة احتياطية من القاعدة.
ثلاث قواعد غير قابلة للتفاوض لنسخة Strapi في الإنتاج: S3 للملفّات المرفوعة (@strapi/provider-upload-aws-s3) — تبقى وسائطك بعد أيّ إعادة إنشاء للحاوية ولا تضخّم قرص خادم VPS؛ pg_dump يومي آلي منسوخ خارج الخادم — فالنسخة الاحتياطية المحلّية وحدها تختفي مع خادم VPS؛ NODE_ENV=production حتمًا — يعيد وضع التطوير تصريف لوحة الإدارة أثناء التشغيل، ويكشف Content-Type Builder، ويعطّل تحسينات التخزين المؤقّت، وهو ما يجب تجنّبه في الإنتاج.
Strapi وواجهة أمامية Nuxt أو Next.js على خادم VPS واحد
من الممكن تمامًا استضافة Strapi وواجهة أمامية على خادم VPS واحد، بشرط توفّر ذاكرة كافية — احسب 8 جيجابايت لكليهما، إذ قد ينطلق البناءان في اللحظة نفسها. وتستخدم البنية الأبسط Nginx موزّعًا: تُوكَّل الطلبات المتّجهة نحو cms.your-domain.com إلى المنفذ 1337 (Strapi)، وتلك المتّجهة نحو your-domain.com إلى المنفذ 3000 (Nuxt أو Next.js). ويكشف Strapi واجهة REST الخاصّة به على /api — وتستهلكها الواجهة الأمامية مباشرةً على شبكة Docker الداخلية، دون المرور من جديد بـ Nginx، ممّا يقلّل زمن الاستجابة. وإن كانت واجهتك الأمامية تولّد صفحات ساكنة (nuxt generate، أو next build بتصدير ساكن)، فبإمكان Nginx خدمة الملفّات من القرص وتوكيل المسارات الديناميكية وحدها. وهذه البنية الأحادية مثالية لمشروع متوسّط الحجم — خادم واحد، وشهادة TLS واحدة، ونقطة إشراف واحدة. وإن فضّلت فصل الواجهة الأمامية، فأدلّتنا لنشر Nuxt وNode.js تعيد المنهج نفسه على خادم VPS Cloud ثانٍ مهيّأ للبناء.