نشر تطبيق Laravel باستخدام GitHub Actions
- نُشر في
- مدة القراءة
- 10 دقائق قراءة
أطلق تطبيق Laravel: جهّز CloudPanel على خادم VPS، وأمّنه عبر Cloudflare، ودع GitHub Actions يختبر وينشر كل تعديل تدفعه تلقائيًا.
بناء خط CI/CD لتطبيقك من أجمل ما يمكنك فعله بعد الانتهاء من التطوير، أو حين تريد خادم staging لتجربة مشروعك عليه. في هذا الدليل أشرح المسار الكامل الذي أتبعه لإطلاق تطبيق Laravel: تجهيز خادم VPS جديد باستخدام CloudPanel، ووضعه خلف Cloudflare، واستنساخ المستودع، ثم ربط GitHub Actions بحيث يُختبر كل دفع (push) إلى فرع master ويُنشر تلقائيًا.
اتبع الخطوات بالترتيب وستحصل في النهاية على تطبيق ينشر نفسه بنفسه.
1. تثبيت CloudPanel

للبدء في العمل على خادم جديد تحتاج عادةً إلى تثبيت الكثير من الحزم، أو استخدام حاوية Docker. أما الطريقة السهلة لبدء نشر تطبيق Laravel بلغة PHP فهي الحصول على VPS من أي مزوّد خدمة مثل AWS EC2 أو Hetzner Cloud. بعد حصولك على الـ VPS، ويجب أن يعمل بنظام Ubuntu 22.04، يمكنك متابعة هذا الدرس.
ملاحظة: كُتب هذا الدليل عام 2023 لنظام Ubuntu 22.04؛ راجع توثيق CloudPanel لمعرفة الإصدارات المدعومة حاليًا.
ما هو CloudPanel؟
CloudPanel لوحة تحكم مجانية وحديثة لإعداد الخوادم وإدارتها، تركّز بشدة على البساطة.
تتيح لك تشغيل تطبيقات PHP وNode.js والمواقع الثابتة والوكلاء العكسيين (Reverse Proxies) وتطبيقات Python في وقت قياسي على بنية تقنية عالية الأداء.
وتدعم الإطلاق السريع على:
- Amazon Web Services
- Digital Ocean
- Hetzner Cloud
- Google Compute Engine
- Microsoft Azure
- Oracle Cloud
- Vultr
- مزوّدون آخرون

المزايا
- مجانية
- سهلة الاستخدام
- مدعومة من المجتمع
- تحميل صفحات فائق السرعة، أسرع حتى 250 مرة
- آمنة (شهادات SSL/TLS مجانية)
- تكامل مع Cloudflare
- أداء عالٍ
- جاهزة للعمل خلال دقيقة واحدة
- تدعم كل المنصات السحابية الكبرى
- تدعم معماريتي x86 وARM
التثبيت على الخادم
-
سجّل الدخول إلى خادمك من الطرفية عبر SSH. إن كنت تسجّل الدخول بكلمة مرور فالأمر هو:
ssh root@yourIpAddressوإن كنت تستخدم مفتاحًا خاصًا فالأمر هو:
ssh -i path_to_your_private_key root@yourIpAddress -
أنت الآن على حساب root والخادم فارغ. قبل تثبيت اللوحة، حدّث النظام وأضف الحزم التي يحتاجها المثبّت:
apt update && apt -y upgrade && apt -y install curl wget sudo -
الآن ثبّت CloudPanel. يُنزَّل سكربت التثبيت ويُتحقق من بصمته SHA-256، ولا يُنفَّذ إلا بعد ذلك:
curl -sS https://installer.cloudpanel.io/ce/v2/install.sh -o install.sh; \ echo "3c30168958264ced81ca9b58dbc55b4d28585d9066b9da085f2b130ae91c50f6 install.sh" | \ sha256sum -c && sudo bash install.sh
الدخول إلى CloudPanel
الأمان أولًا: ادخل إلى CloudPanel بأسرع ما يمكن بعد التثبيت وأنشئ المستخدم المدير. هناك نافذة زمنية قصيرة يمكن فيها للبوتات إنشاء هذا المستخدم قبلك. وإن أمكن، افتح المنفذ 8443 لعنوان IP الخاص بك فقط عبر الجدار الناري.
يمكنك الآن فتح CloudPanel من المتصفح على العنوان https://yourIpAddress:8443.
تجاهل تحذير الشهادة الموقّعة ذاتيًا: اضغط Advanced ثم Proceed للمتابعة إلى CloudPanel.

2. ربط Cloudflare

أؤمن بأن الأمان والتخزين المؤقت يجعلان أي تطبيق أفضل، لذلك نؤمّن الخادم بوضعه خلف الوكيل العكسي لنظام DNS في Cloudflare والاستفادة من قدراته في التخزين المؤقت.
إنشاء حساب Cloudflare
إنشاء حساب في Cloudflare سهل كأي موقع آخر: اذهب إلى صفحة التسجيل وأنشئ حسابًا جديدًا.
إضافة نطاقك إلى Cloudflare

- من لوحة Cloudflare افتح تبويب Websites واضغط زر Add a site على اليمين.
- أدخل نطاقك، وستعطيك Cloudflare خوادم الأسماء (nameservers) الخاصة بها.
- اذهب إلى مزوّد النطاق واستبدل خوادم الأسماء بخوادم Cloudflare.
قد تستغرق هذه العملية حتى 24 ساعة لدى بعض المزوّدين. أنا أستخدم Google Domains ولا تستغرق سوى 10 دقائق، وبعدها يصبح نطاقك فعّالًا على Cloudflare.
ملاحظة: توقفت خدمة Google Domains منذ ذلك الحين؛ وتغيير خوادم الأسماء يتم بالطريقة نفسها لدى أي مسجّل نطاقات.
ربط نطاق بـ CloudPanel
- بعد تفعيل النطاق في Cloudflare افتحه واذهب إلى تبويب DNS.
- أضف سجل
Aجديدًا يوجّهcp.إلى عنوان IP الخادم. تأكد من أن حالة الوكيل (Proxy status) هي DNS only. - في CloudPanel افتح Admin Area ثم Settings، وغيّر النطاق المخصص للوحة إلى
cp.YOUR_DOMAIN.
سيولّد CloudPanel شهادة SSL تلقائيًا ويربط النطاق، ومن الآن يمكنك الوصول إلى اللوحة عبر نطاقك الخاص.
ربط نطاق المشروع

- في CloudPanel افتح تبويب Websites واضغط Add Site، واختر Create a PHP Site، واملأ البيانات ثم اضغط Create. سيضيف CloudPanel قالب nginx وPHP-FPM لنطاقك.
- في Cloudflare أضف سجل DNS من نوع
Aلنطاق المشروع يشير إلى خادمك. - في Cloudflare أيضًا افتح تبويب SSL/TLS وأنشئ شهادة Origin Server جديدة. ستحصل على شهادة صالحة لمدة 15 عامًا مجانًا. انسخها.
- عُد إلى CloudPanel، واختر موقعك الجديد، وافتح تبويب SSL/TLS الخاص به، واضغط Actions ثم Import Certificate، والصق شهادة Cloudflare.

أصبح نطاقك الآن مربوطًا ومؤمّنًا بشهادة SSL. افتحه في المتصفح وتأكد من أنه يعمل.
3. استنساخ المستودع
منح الخادم صلاحية الوصول إلى GitHub
-
سجّل الدخول عبر SSH بمستخدم الموقع الذي أنشأه CloudPanel للمشروع:
ssh youproject@yourIpAddressأو باستخدام مفتاح خاص:
ssh -i path_to_your_private_key root@yourIpAddress -
بهذا المستخدم، ولّد مفتاح SSH لربط الخادم بمشروعك على GitHub:
ssh-keygen -t ed25519 -C "[email protected]"يمكنك الضغط على Enter عند كل سؤال. عند الانتهاء سيطبع مسار المفتاح العام الذي ينتهي بـ
.pub. -
اعرض المفتاح العام وانسخ محتواه:
cat ~/.ssh/id_ed25519.pubالمسار
~/.ssh/id_ed25519.pubهو المسار الافتراضي لمفتاح ed25519؛ استخدم المسار الذي طبعهssh-keygenإن كان مختلفًا لديك. -
في مستودعك على GitHub افتح Settings ثم Deploy keys، واضغط Add deploy key، وأعطه اسمًا، والصق المفتاح ثم احفظه.
استنساخ المشروع
أصبح لخادمك الآن صلاحية الوصول إلى المستودع. انتقل إلى مسار المشروع، ويبدو هكذا:
cd /home/yourproject/htdocs/yourproject.com
ثم استنسخ المستودع داخل المجلد الحالي (لاحظ النقطة . في آخر الأمر):
git clone [email protected]:tomatophp/tomato.git .
إعداد ملف .env
انسخ ملف البيئة النموذجي:
cp .env.example .env
أنشئ قاعدة بيانات جديدة من تبويب Databases في CloudPanel، ثم أضف بياناتها إلى .env:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=tomato-kit
DB_USERNAME=root
DB_PASSWORD=your_password
ثم غيّر الرابط الرئيسي للتطبيق:
APP_URL=https://yourproject.com
APP_HOST=yourproject.com
تثبيت حزم Composer وتنظيف المشروع
ثبّت حزم Composer:
composer install
ثم شغّل أوامر مشروعك، وأهمها:
php artisan key:generate
php artisan config:cache
php artisan storage:link
php artisan optimize:clear
تثبيت NVM وnpm وYarn
أصبح التطبيق جاهزًا لملفات الواجهة الأمامية، ولهذا تحتاج إلى npm.
-
ثبّت nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash -
أعد تحميل بيئة الطرفية الحالية:
source ~/.bashrc -
ثبّت إصدار Node.js الذي تحتاجه، مثل 18:
nvm install 18 -
فعّله:
nvm use 18 -
تحقق من إصدار Node.js:
node -v -
ثبّت Yarn على مستوى النظام:
npm -g i yarn -
ابنِ ملفات الواجهة:
yarn && yarn build
ملاحظة: كان nvm v0.39.2 وNode.js 18 هما الأحدث عام 2023؛ والإصدارات الأحدث تعمل بالطريقة نفسها.
مشروعك جاهز الآن ويمكنك مشاهدته في المتصفح.
4. إنشاء Workflow في GitHub Actions
الخطوة التالية في بناء CI/CD هي ملف workflow بصيغة YAML داخل المستودع. في مشروعك أنشئ مجلد .github، وبداخله مجلد workflows، وبداخله ملف ci.yaml:
mkdir .github
cd .github
mkdir workflows
cd workflows
touch ci.yaml
أضف هذا الـ workflow إلى ci.yaml:
name: Testing Laravel with MySQL
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
laravel:
name: Laravel (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
env:
DB_DATABASE: laravel
DB_USERNAME: root
DB_PASSWORD: password
BROADCAST_DRIVER: log
CACHE_DRIVER: redis
QUEUE_CONNECTION: redis
SESSION_DRIVER: redis
services:
mysql:
image: mysql:8.0
env:
MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: laravel
ports:
- 3306/tcp
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis
ports:
- 6379/tcp
options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
fail-fast: false
matrix:
php-versions: ['8.2']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: sqlite, pdo_sqlite, pcntl, zip, intl, exif, mbstring, dom, fileinfo, mysql
coverage: xdebug
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Prepare Laravel Application
run: |
php -r "file_exists('.env') || copy('.env.example', '.env');"
php artisan key:generate
- name: Clear Config
run: php artisan config:clear
- name: Run Migration
run: php artisan migrate -v
env:
DB_PORT: ${{ job.services.mysql.ports['3306'] }}
REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
- name: Test with phpunit
run: vendor/bin/phpunit --coverage-text
env:
DB_PORT: ${{ job.services.mysql.ports['3306'] }}
REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
- name: Install Yarn dependencies
run: yarn
- name: Compile assets
run: yarn build
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
port: ${{ secrets.SSH_PORT }}
password: ${{ secrets.SSH_PASSWORD }}
script: cd ${{ secrets.SSH_PATH }} && ./.scripts/deploy.sh
ماذا يفعل هذا الـ workflow؟
يعمل الـ workflow مع كل push أو pull request إلى فرع master. يشغّل خدمتي MySQL وRedis، ويثبّت PHP واعتماديات Composer، وينفّذ الـ migrations واختبارات PHPUnit، ويبني ملفات الواجهة. ولا تتصل الخطوة الأخيرة بخادمك عبر SSH لتشغيل سكربت النشر إلا بعد نجاح كل ذلك.
إضافة أسرار الخادم
تقرأ خطوة SSH بيانات الخادم من أسرار GitHub (secrets). في مستودعك على GitHub افتح Settings ثم Secrets and variables ثم Actions، وأضف سرًّا جديدًا لكل مفتاح. على سبيل المثال، يحمل SSH_HOST عنوان IP الخادم. ستحتاج إلى كل هذه المفاتيح:
SSH_HOST: 8.8.8.8
SSH_USERNAME: yourproject
SSH_PORT: 22
SSH_PASSWORD: yourpassword
SSH_PATH: /home/yourproject/htdocs/yourdomain.com
ثم تأكد من أن .env.example يطابق قاعدة البيانات التي ينشئها الـ workflow، لأنه ينسخه إلى .env:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=password
ملاحظة: كان actions/cache@v3 هو الإصدار الحالي وقت كتابة المقال؛ وقد أصدرت GitHub إصدارات رئيسية أحدث منذ ذلك الحين.
5. كتابة سكربت النشر
سكربت النشر هو ما يُنفَّذ على الخادم في كل مرة تدفع فيها تعديلاتك، لذا يجب أن يحتوي على كل أمر تريد تشغيله مع كل عملية نشر.
أنشئ مجلد .scripts وبداخله ملف deploy.sh، وامنح الملف الصلاحية 755 ليكون قابلًا للتنفيذ:
mkdir .scripts
cd .scripts
touch deploy.sh
chmod 755 deploy.sh
أضف هذا السكربت إلى الملف وعدّله كما تشاء:
#!/bin/bash
set -e
echo "Deployment started ..."
# Enter maintenance mode or return true
# if already is in maintenance mode
(php artisan down) || true
# Pull the latest version of the app
git reset --hard
git pull origin master
# Install composer dependencies
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
# Clear the old cache
php artisan clear-compiled
# Recreate cache
php artisan optimize
# Compile npm assets
yarn
yarn build
# Run database migrations
php artisan migrate --force
# Exit maintenance mode
php artisan up
echo "Deployment finished!"
يضع السكربت التطبيق في وضع الصيانة، ويسحب أحدث نسخة من الكود، ويثبّت اعتماديات الإنتاج، ويعيد بناء الذاكرة المؤقتة وملفات الواجهة، وينفّذ الـ migrations، ثم يعيد التطبيق للعمل. وبفضل set -e يتوقف عند أول أمر يفشل.
الخلاصة
ادفع كل هذه التعديلات إلى مستودعك على GitHub. افتح تبويب Actions وسترى الـ workflow يعمل، وبمجرد انتهائه تصبح التحديثات منشورة على خادمك.
من الآن فصاعدًا، يُختبر كل push إلى master أولًا ولا يُنشر إلا إذا نجحت الاختبارات، بينما يدير CloudPanel الخادم وتحميه Cloudflare وتخزّن محتواه مؤقتًا.