أكثر طريقة موثوقة لنشر تطبيق Laravel أو Node.js من Git هي أن يسحب الخادم الكود عبر مفتاح نشر SSH للقراءة فقط، ويبنيه في مجلد إصدار جديد مستقل، ثم يحوّل رابطًا رمزيًا اسمه current إلى هذا الإصدار في خطوة واحدة. بهذا لا يرى الزوار نسخة نصف منشورة، ويصبح التراجع (Rollback) مجرد إعادة الرابط إلى الإصدار السابق.
نشرح هنا البنية والخطوات لكل إطار، ثم نجمعها في سكربت bash وقائمة تحقق.
ما الذي يدخل مستودع Git وما الذي لا يدخله أبدًا؟
مستودع Git يحمل الكود وملفات القفل فقط. كل ما يُولَّد أو يختلف بين البيئات يبقى خارجه:
.env: يحوي كلمات مرور قواعد البيانات والمفاتيح. لا يدخل المستودع أبدًا، حتى لو كان المستودع خاصًا.vendor/وnode_modules/: تُثبَّت على الخادم من ملفات القفل.- السجلات والملفات المرفوعة: محتوى
storage/في Laravel، ومجلدات الرفع في تطبيقات Node.js. - ناتج البناء مثل
public/build، إلا إذا قررت البناء في CI/CD ونشر الناتج كحزمة.
في المقابل، احرص على إدخال composer.lock وpackage-lock.json. بدونهما لا تضمن أن الخادم يثبّت النسخ نفسها التي اختبرتها، ولن يعمل npm ci أصلًا.
.env
.env.*
!.env.example
/vendor/
/node_modules/
/public/build/
/public/storage
/storage/*.keyكيف يسحب الخادم الكود بأمان؟ مفاتيح النشر عبر SSH
لا تضع اسم مستخدم ورمز وصول شخصي داخل رابط المستودع على الخادم. أنشئ زوج مفاتيح خاصًا بالخادم، وأضف المفتاح العام إلى المستودع بوصفه مفتاح نشر للقراءة فقط. هكذا يقتصر أثر أي تسرّب على مستودع واحد وبصلاحية قراءة.
ssh-keygen -t ed25519 -f ~/.ssh/deploy_myapp -N "" -C "deploy@myapp-server"
cat ~/.ssh/deploy_myapp.pub # أضفه مفتاح نشر في إعدادات المستودع
# ~/.ssh/config
# Host git-myapp
# HostName github.com
# User git
# IdentityFile ~/.ssh/deploy_myapp
# IdentitiesOnly yes
ssh -T git-myapp # تحقق من بصمة الخادم قبل قبولها
git clone git-myapp:company/myapp.gitإذا كانت لديك عدة تطبيقات، فلكل تطبيق مفتاح واسم مستعار في ~/.ssh/config، لأن بعض منصات Git لا تسمح باستخدام مفتاح النشر نفسه في أكثر من مستودع. على استضافة cPanel تحتاج إلى الوصول إلى SSH أو إلى Terminal لتنفيذ هذه الخطوات.
لماذا مجلد releases ورابط current؟
/var/www/myapp/
├── releases/
│ ├── 20261024-1830/
│ └── 20261025-0915/
├── shared/
│ ├── .env
│ └── storage/
└── current -> releases/20261025-0915- كل نشر يُبنى في مجلد جديد تحت
releases/، والإصدار الحالي يستمر في خدمة الطلبات أثناء البناء. - ما يجب أن يبقى بين الإصدارات يعيش في
shared/ويُربط داخل كل إصدار. - جذر موقع الويب في nginx أو Apache يشير إلى
current/public. - التحويل يتم بإنشاء رابط مؤقت ثم نقله فوق
currentبالأمرmv -T. هذه عملية إعادة تسمية ذرية. أماln -sfnوحده فيحذف الرابط ثم ينشئه، فتبقى لحظة بلا رابط. - التراجع هو تحويل
currentإلى المجلد السابق. احتفظ بآخر خمسة إصدارات مثلًا واحذف الأقدم.
تنبيه خاص بـ PHP: يخزّن PHP-FPM وOPcache المسارات المحلولة للملفات. بعد التحويل أعد تحميل PHP-FPM بأمر reload، أو مرّر $realpath_root بدل $document_root في إعدادات nginx، حتى لا يستمر تنفيذ ملفات الإصدار القديم.
ما خطوات نشر Laravel بالترتيب؟
داخل مجلد الإصدار الجديد، وقبل التحويل:
composer install --no-dev --optimize-autoloader --no-interactionلتثبيت حزم الإنتاج فقط مع خريطة تحميل محسّنة.- ربط
shared/.envوshared/storageداخل الإصدار. php artisan migrate --forceلتنفيذ الترحيلات دون سؤال تأكيد. الخيار--forceمطلوب في بيئة الإنتاج.php artisan config:cacheوphp artisan route:cacheوphp artisan view:cache. بعدconfig:cacheلا تُقرأ الدالةenv()خارج ملفاتconfig/، فتأكد أن الكود لا يستدعيها مباشرة.php artisan storage:linkلإنشاء رابطpublic/storage.
وبعد التحويل:
- إعادة تحميل PHP-FPM.
php artisan queue:restart: يطلب من عمال الطوابير التوقف بعد إنهاء المهمة الحالية، ثم يعيد Supervisor أو systemd تشغيلهم على الكود الجديد. بدون هذه الخطوة يستمر العمال في تنفيذ الكود القديم من الذاكرة.
راجع أيضًا توثيق النشر في Laravel لإعدادات خادم الويب الموصى بها.
ما خطوات نشر Node.js؟
npm ci: يثبّت بالضبط ما فيpackage-lock.json، ويفشل إن لم يتطابق معpackage.json. هذا ما تريده في النشر، بخلافnpm install.npm run buildإن كان للتطبيق خطوة بناء، ثمnpm prune --omit=devلحذف حزم التطوير.- فحص الصحة قبل التحويل: شغّل الإصدار الجديد على منفذ مؤقت، واستدعِ مسار فحص مثل
/healthبالأمرcurl، ولا تحوّلcurrentإلا إذا نجح الفحص. - أعد تشغيل الخدمة عبر مدير عمليات، ثم افحص الصحة مرة أخرى، وتراجع تلقائيًا إن فشل الفحص.
مدير العمليات يعيد تشغيل التطبيق عند تعطله وعند إعادة تشغيل الخادم. systemd متوفر على أغلب التوزيعات ولا يحتاج إلى أي إضافة. PM2 خيار شائع في بيئة Node.js، ويوفر أمر pm2 reload لإعادة التحميل التدريجي في وضع cluster. في الحالتين اجعل مسار العمل current لا مسار الإصدار نفسه.
# /etc/systemd/system/myapp.service
[Unit]
Description=myapp Node.js
After=network.target
[Service]
User=myapp
WorkingDirectory=/var/www/myapp/current
EnvironmentFile=/var/www/myapp/shared/.env
ExecStart=/usr/bin/node dist/server.js
Restart=on-failure
[Install]
WantedBy=multi-user.targetأين تحفظ متغيرات البيئة والأسرار؟
- ملف
.envواحد لكل بيئة فيshared/، يملكه مستخدم التطبيق بصلاحيات600. - لا تطبع الأسرار في مخرجات سكربت النشر ولا في سجلات CI/CD.
- لا تغيّر
APP_KEYفي Laravel على تطبيق يعمل، فهو يفك تشفير الجلسات والبيانات المشفّرة. - إن أردت حفظ الإعدادات مع الكود، يوفر Laravel الأمر
php artisan env:encryptلتشفير الملف، ويبقى مفتاح فك التشفير خارج المستودع. - في Node.js مرّر المتغيرات عبر
EnvironmentFileفي systemd بدل ملفات داخل مجلد الإصدار.
كيف تتعامل مع ترحيلات قاعدة البيانات دون توقف؟
الترحيلات تُنفَّذ قبل التحويل، أي أن الكود القديم يعمل على المخطط الجديد لفترة قصيرة. لذلك يجب أن يكون كل ترحيل متوافقًا مع الإصدار السابق:
- التوسيع ثم التقليص: أضف العمود الجديد قابلًا لقيمة فارغة، وانشر كودًا يكتب في العمودين، ثم انقل البيانات القديمة، واحذف العمود القديم في نشر لاحق.
- لا تعِد تسمية عمود أو جدول في خطوة واحدة.
- تعديل الجداول الكبيرة قد يقفلها. اختبر الترحيل على نسخة من قاعدة البيانات الحقيقية وقِس مدته قبل الإنتاج.
- التراجع عن الكود لا يتراجع عن قاعدة البيانات. اكتب ترحيلات تتقدم للأمام فقط، وخذ نسخة احتياطية قبل كل ترحيل. انظر استراتيجية النسخ الاحتياطي 3-2-1.
مثال: سكربت نشر بلغة bash لتطبيق Laravel
#!/usr/bin/env bash
set -euo pipefail
APP=/var/www/myapp
REPO=git-myapp:company/myapp.git
BRANCH=${1:-main}
RELEASE=$APP/releases/$(date +%Y%m%d-%H%M%S)
KEEP=5
echo "==> Cloning $BRANCH"
git clone --depth 1 --branch "$BRANCH" "$REPO" "$RELEASE"
echo "==> Linking shared files"
rm -rf "$RELEASE/storage"
ln -s "$APP/shared/storage" "$RELEASE/storage"
ln -s "$APP/shared/.env" "$RELEASE/.env"
cd "$RELEASE"
echo "==> Installing dependencies"
composer install --no-dev --optimize-autoloader --no-interaction
npm ci && npm run build && rm -rf node_modules
echo "==> Migrating and caching"
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan storage:link
echo "==> Switching current"
ln -s "$RELEASE" "$APP/current.tmp"
mv -T "$APP/current.tmp" "$APP/current"
sudo systemctl reload php8.3-fpm
php artisan queue:restart
echo "==> Health check"
if ! curl -fsS --max-time 10 https://example.com/up > /dev/null; then
echo "Health check failed, rolling back"
PREV=$(ls -1d "$APP"/releases/* | sort | tail -n 2 | head -n 1)
ln -s "$PREV" "$APP/current.tmp"
mv -T "$APP/current.tmp" "$APP/current"
sudo systemctl reload php8.3-fpm
exit 1
fi
echo "==> Cleaning old releases"
ls -1d "$APP"/releases/* | sort | head -n -"$KEEP" | xargs -r rm -rfالسكربت مثال يحتاج إلى تكييف: اسم خدمة PHP-FPM، ومسار فحص الصحة (المسار /up موجود في إصدارات Laravel الحديثة)، وصلاحيات sudo. ولاحظ أن التراجع هنا يعيد الكود فقط ولا يعيد قاعدة البيانات.
ماذا عن Git Version Control في cPanel؟
يوفر cPanel واجهة Git Version Control لإنشاء مستودعات Git داخل الحساب أو استنساخها من مصدر خارجي. تعتمد عمليات النشر فيها على ملف .cpanel.yml في جذر المستودع، يحوي قائمة أوامر shell تُنفَّذ عند النشر. يمكن أن يحدث النشر تلقائيًا عند الدفع إلى مستودع مستضاف على الحساب، أو يدويًا بسحب التحديثات من المصدر ثم الضغط على Deploy HEAD Commit. ويشترط cPanel وجود الملف وأن تكون شجرة العمل نظيفة.
---
deployment:
tasks:
- export DEPLOYPATH=/home/username/myapp/
- /bin/cp -R app config public routes $DEPLOYPATHهذه الطريقة لا تنشئ مجلدات إصدارات ولا توفر التراجع من تلقاء نفسها. يمكنك استدعاء سكربت مثل السابق من داخل المهام إن أردت هذا السلوك.
قائمة تحقق قبل كل نشر
- لا يوجد
.envأوvendorأوnode_modulesفي المستودع، وملفات القفل موجودة. - الخادم يسحب عبر مفتاح نشر للقراءة فقط.
- كل إصدار في مجلد مستقل، والتحويل يتم بالأمر
mv -T. - الترحيلات متوافقة مع الإصدار السابق، ومعها نسخة احتياطية حديثة.
- ذاكرات Laravel المؤقتة تُبنى داخل الإصدار الجديد قبل التحويل.
- PHP-FPM يُعاد تحميله، وعمال الطوابير يُعاد تشغيلهم.
- فحص الصحة ينجح قبل التحويل وبعده، والتراجع مجرَّب فعلًا لا نظريًا.
كيف يتعامل bPanel مع النشر من Git؟
يوفّر bPanel النشر من Git مع سجل لعمليات النشر وتراجع بنقرة إلى أي إصدار سابق، اعتمادًا على البنية نفسها التي شرحناها هنا. جرّبه على خوادمك من ابدأ مع bPanel.
الخلاصة
- أبقِ الأسرار والتبعيات وناتج البناء خارج Git، وأدخل ملفات القفل.
- اسحب الكود عبر مفتاح نشر SSH للقراءة فقط.
- ابنِ كل إصدار في مجلد مستقل، وحوّل
currentبخطوة ذرية، واحتفظ بإصدارات سابقة للتراجع. - اجعل الترحيلات متوافقة مع الإصدار السابق، وافحص الصحة قبل التحويل وبعده.