الخطوات
-
1
أنشئ مجلد الـ workflow والملف
يبحث GitHub Actions عن ملفات الـ workflow في
.github/workflows/. أي ملف.ymlهناك يُلتقط تلقائياً. ملف واحد = workflow واحد.bashmkdir -p .github/workflows touch .github/workflows/deploy.yml -
2
اكتب trigger ووظيفة الاختبار
شغَّل عند الـ
pushإلىmain. وظيفةtestتسحب الكود وتثبّت التبعيات وتشغّل مجموعة الاختبارات. هذا المثال يستخدم PHP/Laravel — استبدل action الإعداد وأمر التثبيت لـ Node أو Python وغيرها.yamlname: CI/CD on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest services: mysql: image: mysql:8.4 env: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: laravel_test ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: "8.3" extensions: mbstring, pdo_mysql, zip, bcmath coverage: none - name: Cache Composer packages uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-php-${{ hashFiles("**/composer.lock") }} restore-keys: ${{ runner.os }}-php- - name: Install dependencies run: composer install --no-dev --optimize-autoloader --no-interaction - name: Copy .env run: cp .env.example .env && php artisan key:generate - name: Run tests env: DB_CONNECTION: mysql DB_HOST: 127.0.0.1 DB_PORT: 3306 DB_DATABASE: laravel_test DB_USERNAME: root DB_PASSWORD: secret run: php artisan test --parallel -
3
أضف وظيفة النشر عبر SSH
تعمل وظيفة
deployفقط إن نجحتtest(needs: test) وعند الـ push إلىmainفقط (لا عند الـ PRs). تتصل بخادمك عبر SSH وتشغّل أوامر النشر.yamldeploy: runs-on: ubuntu-latest needs: test if: github.ref == "refs/heads/main" && github.event_name == "push" steps: - name: Deploy to server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_KEY }} port: 22 script: | cd /var/www/myapp git pull origin main composer install --no-dev --optimize-autoloader php artisan migrate --force php artisan optimize php artisan view:cache chown -R www-data:www-data /var/www/myapp -
4
احفظ الأسرار في GitHub
لا تضع أي بيانات اعتماد في ملف YAML. احفظها كأسرار للمستودع وأشر إليها بـ
${{ secrets.NAME }}. انتقل إلى مستودعك ← Settings ← Secrets and variables ← Actions ← New repository secret.bash# الأسرار التي يجب إنشاؤها في GitHub: # SSH_HOST — عنوان IP الخادم أو اسم المضيف # SSH_USER — مثل root أو deploy # SSH_KEY — المفتاح الخاص (المحتوى الكامل لـ ~/.ssh/id_ed25519) # أنشئ مفتاح نشر مخصصاً (لا تعيد استخدام مفتاحك الشخصي) ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/deploy_key -N "" # أضف المفتاح العام إلى الخادم ssh-copy-id -i ~/.ssh/deploy_key.pub user@your-server # الصق محتوى المفتاح الخاص في SSH_KEY في GitHub cat ~/.ssh/deploy_key -
5
استخدم أسرار البيئة للـ staging مقابل الإنتاج
لبيئات متعددة، أنشئ Environments في GitHub (Settings ← Environments). يمكن لكل بيئة أن يكون لها أسرارها الخاصة وقواعد الحماية (مراجعون مطلوبون) وقيود فروع النشر.
yamldeploy: runs-on: ubuntu-latest needs: test environment: production # يرتبط بـ GitHub Environment if: github.ref == "refs/heads/main" && github.event_name == "push" steps: - name: Deploy to production uses: appleboy/ssh-action@v1 with: host: ${{ secrets.SSH_HOST }} # محدود ببيئة "production" username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_KEY }} script: cd /var/www/myapp && git pull && php artisan optimize -
6
مرّر ملفات البناء بين الوظائف
إن كانت خطوة البناء تنتج ملفاً مجمّعاً (أصول Vite أو ملف ثنائي أو digest لصورة Docker)، ارفعه في وظيفة
testوحمّله فيdeploy— لا تُعد البناء في وظيفة النشر.yaml# في وظيفة test — ارفع الأصول المجمّعة - name: Build frontend run: npm ci && npm run build - uses: actions/upload-artifact@v4 with: name: frontend-build path: public/build/ deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: frontend-build path: public/build/ # ثم انقل مجلد public/build/ إلى الخادم بـ rsync أو scp -
7
أضف بناء matrix لإصدارات متعددة
تشغّل بنية الـ matrix نفس الوظيفة عبر إصدارات متعددة بالتوازي — مفيد للمكتبات أو ضمانات دعم إصدارات متعددة. كل مجموعة وظيفة منفصلة في واجهة Actions.
yamltest: runs-on: ubuntu-latest strategy: matrix: php: ["8.2", "8.3"] # node: [18, 20, 22] # استبدل لمشاريع Node steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - run: composer install --no-interaction - run: php artisan test -
8
استكشف أخطاء الـ workflow وأصلحها
معظم الفشل يأتي من ثلاثة أماكن. تحقق منها بالترتيب:
- سر مفقود — سر فارغ يسبب خطأ SSH غامضاً. أعد التحقق من اسم السر يطابق تماماً (حساس لحالة الأحرف).
- صيغة مفتاح SSH — يجب أن يتضمن المفتاح أسطر
-----BEGIN...-----و-----END...-----. الصقه خاماً بدون مسافات إضافية. - خطأ صلاحية الخادم — يجب أن يملك مستخدم النشر مجلد التطبيق أو يكون في مجموعة
www-data. شغّلchown -R deploy:deploy /var/www/myappمرة واحدة.
bash# أعد تشغيل workflow فاشل من سطر الأوامر gh run list --limit 5 gh run rerun <run-id> # راقب run مباشرة gh run watch <run-id> # حمّل سجلات run فاشل gh run view <run-id> --log-failed
نصائح ومحاذير
- استخدم مجموعات <code>concurrency</code> لإلغاء الـ runs الجارية عند وصول push جديد — يمنع تراكم عمليات نشر متعددة من commits سريعة: <code>concurrency: { group: deploy, cancel-in-progress: true }</code>
- أضف trigger من نوع <code>workflow_dispatch</code> حتى تتمكن من تشغيل النشر يدوياً من واجهة GitHub دون push: <code>on: [push, workflow_dispatch]</code>
- خزّن تبعيات مدير الحزم مؤقتاً بـ <code>actions/cache@v4</code> مُفهرَسة بـ hash ملف القفل. ذاكرة مؤقتة دافئة تختصر وقت التثبيت من 60 ثانية لأقل من 5 ثوانٍ.
- حافظ على سرعة وظيفة النشر — شغّل <code>php artisan migrate --pretend</code> في وظيفة الاختبار لاكتشاف أخطاء الـ migrations قبل الاتصال بالخادم.
- استخدم <code>actions/checkout@v4</code> مع <code>fetch-depth: 0</code> إن كانت اختباراتك أو أدوات إصدار الإصدارات تحتاج تاريخ git كاملاً.
خاتمة
لديك الآن بيبلاين تشغّل الاختبارات عند كل push، وتحجب النشر عند فشلها، وتشحن للإنتاج عبر SSH حين تكون الفرع main والاختبارات خضراء. وسّعها تدريجياً — أضف فحوصات جودة الكود (phpstan، eslint)، أو إشعارات (Slack، بريد إلكتروني)، أو بوابة بيئة staging قبل الإنتاج.