آموزش GitHub Actions برای دیپلوی خودکار روی سرور (CI/CD عملی با SSH)

event 15 مرداد 1405
timer 17 دقیقه
آموزش GitHub Actions برای دیپلوی خودکار روی سرور (CI/CD عملی با SSH)
favorite 1

اگه یک‌خط جواب می‌خوای: GitHub Actions یه فایل YAML داخل .github/workflows/ هست که وقتی کد رو push می‌کنی، خودش وصل میشه به سرورت از طریق SSH، کد جدید رو می‌کشه، migration رو اجرا می‌کنه، و کش‌ها رو پاک می‌کنه — بدون اینکه دستت به ترمینال بخوره. تو این راهنما هم سناریوی بدون Docker (مخصوص پروژه‌های لاراولی سنتی روی Nginx + PHP-FPM) رو نشون می‌دم، هم سناریوی Docker Compose، هم atomic deploy با symlink برای صفر downtime. کدها واقعی‌ان، نه کپی از مستندات انگلیسی.

چرا دیپلوی دستی در ۲۰۲۶ دیگه منطقی نیست؟

سال ۲۰۲۲ رو یادمه — هر بار کد آپدیت می‌شد، باید SSH می‌زدم، git pull می‌کردم، composer install، npm run build، php artisan migrate، view:clear، cache:clear، php-fpm reload. ده دقیقه کار تکراری. یه بار فراموش کردم npm run build رو بزنم و سایت ۳ ساعت با CSS قدیمی بالا بود. اون روز تصمیم گرفتم دیگه دستی deploy نکنم.

از اون به بعد، هر push به main خودش این ۷ مرحله رو می‌ره: pull → install → build → migrate → cache → health check → notify. هیچ‌کدوم رو فراموش نمی‌کنه، هیچ‌وقت ساعت ۲ نصفه شب بیدار نمی‌شم که یه hotfix بزنم. این یعنی CI/CD واقعی — نه صرفاً اجرای تست.

سه مزیت دیگه که بعد از ۳ سال استفاده از GitHub Actions دیدم:

  • اعتبار تیمی بالا میره: هر توسعه‌دهنده‌ای با git push بدون نیاز به دسترسی SSH دیپلوی می‌کنه. دسترسی SSH فقط برای یه deploy key هست.
  • تاریخچه شفاف: همه دیپلوی‌ها تو تب Actions ثبت میشن. کی، کدوم commit، چقدر طول کشید، چه خطایی داد.
  • Rollback یک کلیک: اگه workflow فیل rollback داشته باشه، با یه کلیک تو GitHub UI می‌تونی برگردی به نسخه قبلی.

این مقاله برای کسیه که می‌خواد الان، تو یه بعدازظهر CI/CD رو روی پروژه لاراولی یا Node.js‌ش راه بندازه — نه تئوری، نه آموزش ۱۰ قسمتی.

پیش‌نیازها — قبل از شروع چی لازمه؟

این آموزش برای کسیه که:

  • یه VPS لینوکسی داره (Ubuntu 22.04 یا 24.04) — Hetzner، DigitalOcean، Vultr یا سرور ایرانی، فرقی نمی‌کنه
  • روش Nginx + PHP-FPM یا Docker یه سایت بالا آورده
  • با SSH و مفاهیم پایه Git (branch, push, pull) راحته
  • یه repo روی GitHub داره (public یا private)

اگه با SSH key آشنا نیستی، اول مستندات رسمی OpenSSH رو یه نگاه بنداز. اگه هم Nginx تنظیم نکردی قبلاً، راهنمای Nginx برای لاراول رو ببین.

معماری کلی: GitHub Actions چطور به سرورت وصل میشه

قبل از کد، باید بفهمی پشت‌صحنه چه اتفاقی میفته. GitHub Actions روی یه runner (ماشین مجازی یا اختصاصی) اجرا میشه. وقتی می‌خوای به سرورت SSH بزنی، دو تا گزینه داری:

GitHub Repo
    │
    │ (push to main)
    ▼
GitHub Actions Runner (ubuntu-latest)
    │
    │ SSH connection
    │ using: SSH_PRIVATE_KEY secret
    ▼
Your VPS (Nginx + PHP-FPM + Laravel)

سناریوی ۱ — Hosted runner + SSH action (ساده‌تر): runner خود GitHub کار رو اجرا می‌کنه و با SSH به سرورت وصل میشه. کلید SSH شما هیچ‌وقت لو نمیره چون فقط روی سرور مقصد استفاده میشه.

سناریوی ۲ — Self-hosted runner (سریع‌تر، ارزون‌تر در مقیاس): یه نرم‌افزار روی خود سرورت نصب می‌کنی، runner محلی میشه، دیگه SSH لازم نیست. ولی اجرای کد CI مستقیماً روی prod یه ریسک امنیتیه (مخصوصاً اگه PR از fork بیاد).

برای ۹۰٪ پروژه‌ها، سناریوی ۱ بهتره. ساده‌تر، امن‌تر، و isolation بیشتری داره.

مرحله ۱: آماده‌سازی سرور — کاربر Deploy و SSH Key اختصاصی

اولین و مهم‌ترین قانون امنیتی: هرگز با کاربر root دیپلوی نکن. یه کاربر اختصاصی با دسترسی محدود بساز:

# روی سرورت (با sudo)
sudo adduser deploy --disabled-password
sudo mkdir -p /var/www/myapp
sudo chown deploy:www-data /var/www/myapp

حالا SSH key اختصاصی برای GitHub Actions بساز. این key فقط باید بتونه به کاربر deploy وصل بشه، نه چیز دیگه:

# روی سرورت (بعد از su - deploy)
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/github_actions_key -N ""
cat ~/.ssh/github_actions_key.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

محتوای ~/.ssh/github_actions_key (کلید خصوصی) رو کپی کن — این رو می‌ذاریم تو GitHub Secrets. کلید عمومی همون github_actions_key.pub هست که الان authorized_keys شد.

چرا ed25519؟ کوتاه‌تره، امن‌تره، و در ۲۰۲۶ دیگه RSA توصیه نمیشه. OpenSSH 8.5+ که روی Ubuntu 22.04+ هست، کاملاً ساپورت می‌کنه.

مرحله ۲: تنظیم GitHub Secrets به روش امن

برو تو Settings → Secrets and variables → Actions → New repository secret و اینا رو بساز:

نام Secret مقدار توضیح
SSH_HOST srv1.example.com یا IP آدرس سرور
SSH_USER deploy کاربر SSH
SSH_PORT 22 (پیش‌فرض) یا custom پورت SSH
SSH_PRIVATE_KEY محتوای github_actions_key کلید خصوصی
DEPLOY_PATH /var/www/myapp مسیر پروژه
SLACK_WEBHOOK https://hooks.slack.com/... اختیاری برای notification

نکته امنیتی حیاتی: هیچ‌وقت کلید SSH رو تو کد commit نکن. اگه لو بره، هرکی داشته باشه می‌تونه به سرورت وارد بشه. GitHub Secrets رمزنگاری شده‌ان و فقط داخل runner قابل دسترسی‌ان.

مرحله ۳: نوشتن Workflow — ساده‌ترین حالت

یه فایل به نام .github/workflows/deploy.yml تو ریشه repo بساز:

name: Deploy to Production

on:
  push:
    branches: [main]
  workflow_dispatch: # اجازه دیپلوی دستی از GitHub UI

concurrency:
  group: production-deploy
  cancel-in-progress: false # اگه deploy داشت، جدید رو کنسل کن

jobs:
  deploy:
    name: Deploy to VPS
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Deploy over SSH
        uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT || 22 }}
          script_stop: true
          command_timeout: 8m
          script: |
            cd ${{ secrets.DEPLOY_PATH }}
            echo "==> Pulling latest from origin/main"
            git fetch --prune origin
            git reset --hard origin/main
            git log -1 --pretty=format:'HEAD now at %h %s (%an, %ar)'
            echo ""

این workflow هر push به main اجرا میشه. با git reset --hard origin/main مطمئن میشی دقیقاً همون commitی که push شده روی سرور هست. اگه وسط deploy قطع بشه، دفعه بعد که اجرا بشه، HEAD رو اصلاح می‌کنه.

چرا cancel-in-progress: false؟ اگه وسط deploy یه push جدید بیاد، deploy قبلی کامل بشه. وگرنه ممکنه فایل نیمه‌کاره روی سرور بمونه و سایت خراب بشه.

مرحله ۴: سناریوی واقعی لاراول — بدون Docker

اکثر پروژه‌های ایرانی (و خیلی از پروژه‌های کوچیک تا متوسط دنیا) لاراول رو مستقیم روی Nginx + PHP-FPM اجرا می‌کنن، نه توی Docker. این workflow کاملاً عملیاتی برای این setup هست:

name: Deploy Laravel

on:
  push:
    branches: [main]
  workflow_dispatch:

concurrency:
  group: production
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    environment: production

    steps:
      - name: Setup SSH
        uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT || 22 }}
          script_stop: true
          command_timeout: 12m
          script: |
            set -e
            
            cd ${{ secrets.DEPLOY_PATH }}
            
            echo "==> Pulling latest code"
            git fetch --prune origin
            git reset --hard origin/main
            
            echo "==> Installing PHP dependencies"
            composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
            
            echo "==> Installing Node dependencies"
            npm ci --no-audit
            
            echo "==> Building frontend assets"
            npm run build
            
            echo "==> Running migrations"
            php artisan migrate --force --no-interaction
            
            echo "==> Clearing and caching config"
            php artisan config:cache
            php artisan route:cache
            php artisan view:cache
            
            echo "==> Reloading PHP-FPM"
            sudo systemctl reload php8.3-fpm
            
            echo "==> Health check"
            sleep 2
            curl -sf -o /dev/null -w "HTTP %{http_code} in %{time_total}s\n" https://example.com

نکات کلیدی این workflow:

  • set -e یعنی اگه هر دستوری fail بشه، deploy متوقف میشه. هیچ‌وقت فایل ناقص روی prod نمیره.
  • composer install --no-dev — پکیج‌های dev مثل phpunit و laravel-debugbar رو نصب نمی‌کنه. هم سریع‌تره، هم امن‌تره.
  • npm ci به جای npm install — دقیقاً همون package-lock.json رو می‌خونه، نه package.json. برای CI ضروریه.
  • migrate --force — تو production، Laravel ازت تأیید می‌خواد. این flag غیرفعالش می‌کنه چون داری از CI اجرا می‌کنی.
  • environment: production — یه لایه محافظتی تو GitHub اضافه می‌کنه. می‌تونی required reviewers تنظیم کنی.

یه نکته درباره php artisan optimize: تو لاراول ۱۲ دستور optimize همه کش‌ها رو یکجا می‌گیره. می‌تونی جای ۳ خط آخر، بنویسی php artisan optimize. ولی من ترجیح میدم جدا اجرا کنم چه اگه یکی fail بشه، بفهمم کدوم.

مرحله ۵: سناریوی Docker Compose

اگه پروژه‌ات کانتینریه (معمولاً پروژه‌های Node، Python، یا لاراول با microservice)، workflow فرق می‌کنه:

name: Deploy with Docker

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT || 22 }}
          command_timeout: 10m
          script: |
            set -e
            cd /opt/myapp
            
            echo "==> Pulling latest"
            git fetch --prune origin
            git reset --hard origin/main
            
            echo "==> Pulling new images"
            docker compose pull
            
            echo "==> Rebuilding and restarting"
            docker compose up -d --build --remove-orphans
            
            echo "==> Waiting for healthy state"
            for i in $(seq 1 20); do
              status=$(docker compose ps --format json | jq -r '.[].Health' 2>/dev/null || echo "starting")
              if [ "$status" = "healthy" ]; then
                echo "Container is healthy"
                break
              fi
              echo "Waiting... ($i/20)"
              sleep 3
            done
            
            echo "==> Pruning old images"
            docker image prune -f

تفاوت کلیدی با سناریوی قبلی: اینجا health check داخل همون کار deploy انجام میشه. اگه container بالا نیومد، deploy fail میشه و سایت همون نسخه قبلی می‌مونه (چون image قبلی هنوز هست).

مرحله ۶: Atomic Deploy با Symlink — صفر Downtime واقعی

مشکل git pull ساده اینه: بین چند ثانیه‌ای که فایل‌ها دارن عوض میشن، سایت ۵۰۰ میده. اگه visitor اون لحظه بیاد، error می‌بینه. راه‌حل atomic deploy هست:

# ساختار فولدر روی سرور
/var/www/myapp/
├── releases/
│   ├── 2026-08-05-143022/
│   ├── 2026-08-05-150011/
│   └── 2026-08-06-094530/  ← این release جدید
├── shared/
│   ├── .env           ← بین همه releaseها share میشه
│   └── storage/       ← فایل‌های آپلودی
└── current → releases/2026-08-06-094530/  ← symlink

Nginx باید به /var/www/myapp/current/public اشاره کنه. وقتی symlink رو عوض می‌کنی، اتمیکه — یا old یا new، هیچ حالت واسطه‌ای نیست.

#!/bin/bash
set -e

REPO=/var/www/myapp
RELEASE="$REPO/releases/$(date +%Y-%m-%d-%H%M%S)"
SHARED="$REPO/shared"

# 1. ساخت release جدید
mkdir -p "$RELEASE"
git clone --depth 1 --branch main git@github.com:user/repo.git "$RELEASE"

# 2. لینک کردن shared files
ln -s "$SHARED/.env" "$RELEASE/.env"
rm -rf "$RELEASE/storage"
ln -s "$SHARED/storage" "$RELEASE/storage"

# 3. Build
cd "$RELEASE"
composer install --no-dev --optimize-autoloader
npm ci && npm run build

# 4. اجرای migration (روی release جدید)
php artisan migrate --force

# 5. Optimize
php artisan config:cache
php artisan route:cache
php artisan view:cache

# 6. عوض کردن symlink (اتمیک)
ln -sfn "$RELEASE" "$REPO/current_new"
mv -Tf "$REPO/current_new" "$REPO/current"

# 7. پاک کردن releaseهای قدیمی (نگه‌داشتن ۵ تا)
cd "$REPO/releases"
ls -1t | tail -n +6 | xargs -r rm -rf

echo "✅ Deployed $RELEASE"

این اسکریپت رو بذار تو /var/www/myapp/deploy.sh و توی workflow صدا بزن:

- uses: appleboy/ssh-action@v1.2.0
  with:
    host: ${{ secrets.SSH_HOST }}
    # ...
    script: bash /var/www/myapp/deploy.sh

نکته حیاتی: mv -Tf به جای ln -sfn ساده. چون ln -sfn می‌تونه race condition داشته باشه. mv -Tf اتمیکه روی همه فایل‌سیستم‌ها.

مرحله ۷: Health Check و Notification

بعد از deploy، باید مطمئن بشی سایت واقعاً جواب میده. اضافه کن:

- name: Health check
  run: |
    sleep 5
    STATUS=$(curl -sf -o /dev/null -w "%{http_code}" https://example.com)
    if [ "$STATUS" != "200" ]; then
      echo "❌ Health check failed: HTTP $STATUS"
      exit 1
    fi
    echo "✅ Health check passed"

- name: Notify Slack
  if: always()
  uses: slackapi/slack-github-action@v1.27.0
  with:
    payload: |
      {
        "text": "Deploy ${{ job.status }}: ${{ github.event.head_commit.message }}"
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

if: always() یعنی حتی اگه deploy fail بشه، notification بیاد. اینجوری متوجه میشی deploy خراب شده.

برای Telegram هم می‌تونی از appleboy/telegram-action استفاده کنی — خیلی از تیم‌های ایرانی ترجیح میدن.

مرحله ۸: Rollback سریع وقتی Deploy خراب شد

اتفاق میفته. یه deploy میره بالا، یه bug تو production می‌بینی، باید فوری برگردی. GitHub Actions این کار رو خیلی راحت می‌کنه:

name: Rollback Production

on:
  workflow_dispatch:
    inputs:
      release:
        description: 'Release to rollback to (e.g., 2026-08-05-150011)'
        required: true

jobs:
  rollback:
    runs-on: ubuntu-latest
    steps:
      - uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            set -e
            cd /var/www/myapp
            RELEASE="releases/${{ inputs.release }}"
            
            if [ ! -d "$RELEASE" ]; then
              echo "❌ Release not found: $RELEASE"
              exit 1
            fi
            
            # عوض کردن symlink به release قدیمی
            ln -sfn "$(pwd)/$RELEASE" current_new
            mv -Tf current_new current
            
            cd current
            php artisan config:clear
            php artisan view:clear
            sudo systemctl reload php8.3-fpm
            
            echo "✅ Rolled back to $RELEASE"

حالا تو GitHub UI، تب Actions → Rollback Production → Run workflow → release قبلی رو وارد کن. ۱۰ ثانیه‌ای سایت برمی‌گرده.

Hosted Runner یا Self-hosted؟ (مقایسه واقعی)

این سوالیه که خیلی‌ها می‌پرسن. جدول واقعی:

ویژگی GitHub-hosted runner Self-hosted runner
هزینه ۲۰۰۰ دقیقه رایگان/ماه، بعدش $0.008/دقیقه (Linux) رایگان (الکتریسیته + نگهداری)
سرعت ۲-۴ دقیقه cold start تقریباً فوری
نگهداری صفر — GitHub handle می‌کنه خودت باید آپدیت کنی
امنیت ایزوله، ephemeral روی prod اجرا میشه = ریسک
دسترسی به internal services باید expose کنی یا tunnel بزنی مستقیم به همه چیز دسترسی داره
Concurrency ۲۰ job موازی (free) محدود به منابع سرورت
مناسب برای استارتاپ، تیم کوچیک، پروژه‌های public تیم بزرگ، monorepo، buildهای سنگین

محاسبه هزینه واقعی: اگه روزی ۱۰ deploy داری، هر کدوم ۵ دقیقه، میشه ۱۵۰۰ دقیقه/ماه. کاملاً رایگانه. حتی ۱۰۰ deploy در روز هم تو پلن رایگان جا میشه.

Self-hosted کی منطقیه؟ وقتی build لاراول + Node + assets بیشتر از ۱۰ دقیقه طول می‌کشه، یا وقتی به دیتابیس داخلی یا سرویس‌های private باید وصل بشی. برای ۹۰٪ پروژه‌ها، hosted runner کافیه.

appleboy/ssh-action vs rsync-action vs ssh-command

سه تا action معروف برای SSH. کدوم رو کِی استفاده کنیم؟

Action مزیت عیب بهترین استفاده
appleboy/ssh-action ساده، سریع، community بزرگ script به صورت string پاس میده (escape دردسر) اکثر پروژه‌ها، وقتی deploy ساده‌ست
burnett01/rsync-action فقط فایل sync می‌کنه، سریع‌تر دستور اجرا نمی‌کنه وقتی فقط فایل می‌خوای آپلود کنی (build artifact)
appleboy/scp-action فایل تک منتقل می‌کنه یه فایل، نه whole deployment کانفیگ، .env، secrets
ssh-command (official GitHub) رسمی، supported feature کمتر وقتی به سادگی رسمی بودن نیاز داری

ترکیب طلایی: rsync-action برای انتقال فایل + ssh-action برای اجرای دستور. مثال:

- name: Sync files with rsync
  uses: burnett01/rsync-action@v1.2.0
  with:
    hosts: ${{ secrets.SSH_HOST }}
    key: ${{ secrets.SSH_PRIVATE_KEY }}
    user: ${{ secrets.SSH_USER }}
    source: "./"
    target: "/var/www/myapp/build/"
    args: "-avz --exclude='.git' --exclude='node_modules'"

- name: Run deployment commands
  uses: appleboy/ssh-action@v1.2.0
  with:
    # ...
    script: |
      cd /var/www/myapp/build
      composer install --no-dev --optimize-autoloader
      php artisan optimize
      sudo systemctl reload php8.3-fpm

این الگو مخصوصاً برای build artifact مناسبه: فایل‌ها رو تو runner می‌سازی، فقط خروجی رو به سرور منتقل می‌کنی.

GitHub Actions در مقابل GitLab CI / Jenkins / Deployer

سوال رایج: «چرا GitHub Actions نه Jenkins یا Deployer؟»

ابزار مزیت اصلی بهترین برای هزینه
GitHub Actions یکپارچه با GitHub، صفر ست‌آپ پروژه‌های GitHub رایگان تا ۲۰۰۰ دقیقه/ماه
GitLab CI یکپارچه با GitLab، self-hosted قوی پروژه‌های GitLab رایگان self-hosted
Jenkins flexibility بی‌نهایت، پلاگین‌های فراوان سازمان بزرگ، نیاز خاص رایگان ولی هزینه ops
Deployer (PHP) مخصوص PHP، atomic deploy built-in تیم PHP که می‌خواد صفر وابستگی خارجی رایگان
Envoyer (Laravel) رسمی Laravel، zero-downtime پروژه‌های Laravel بزرگ $۱۰/ماه

پیشنهاد من: اگه repoت GitHub هست، GitHub Actions منطقی‌ترین انتخابه. اگه می‌خوای کاملاً مستقل از هر سرویس خارجی باشی، Deployer PHP رو یاد بگیر. Deployer با dep deploy production همون کار رو می‌کنه ولی روی سرور خودت اجرا میشه. ترکیب GitHub Actions + Deployer هم ممکنه ولی overkill میشه.

اشتباهات رایج + دیباگ (تجربه واقعی captaincode)

تو ۳ سال استفاده از GitHub Actions روی پروژه‌های مختلف، این اشتباهات رو بارها دیدم:

۱. SSH key با passphrase

اگه موقع ساخت key، passphrase دادی، GitHub Actions نمی‌تونه استفاده‌ش کنه. راه‌حل: کلید بدون passphrase بساز (-N ""). چون امنیت SSH key تو GitHub Secrets حفظ میشه.

۲. فراموش کردن set -e تو script

بدون set -e، اگه composer install fail بشه ولی exit code 0 برگردونه (مثلاً وقتی mirror مشکل داره)، workflow موفق میشه ولی کد قدیمی بالا میاد. همیشه set -e اول script بذار.

۳. Migration که دیتا رو خراب می‌کنه

اگه migration توش TRUNCATE یا DROP داره، حتماً backup بگیر قبلش:

# قبل از migrate
mysqldump -u root -p$DB_PASS $DB_NAME | gzip > /var/backups/db-$(date +%s).sql.gz
php artisan migrate --force

۴. Cache قدیمی بعد از deploy

بعضی وقت‌ها بعد از deploy، سایت هنوز کد قدیمی نشون میده چون OPcache یا view:cache قدیمیه. همیشه php artisan optimize:clear قبل از optimize بذار، یا opcache.revalidate_freq=0 تو PHP-FPM.

۵. Concurrency نادرست

اگه cancel-in-progress: true بذاری، ممکنه deploy اول نیمه‌کاره سمت سرور بمونه. برای production همیشه false بذار. برای staging می‌تونی true کنی.

۶. خطای Permission denied روی فایل‌ها

وقتی GitHub Actions فایل می‌ریزه، مالکیتش با کاربر deploy میشه. ولی Nginx و PHP-FPM با کاربر www-data اجرا میشن. حلش:

sudo chown -R deploy:www-data /var/www/myapp
sudo find /var/www/myapp -type d -exec chmod 775 {} \;
sudo find /var/www/myapp -type f -exec chmod 664 {} \;

بذار این خط رو تو deploy.sh بعد از pull.

۷. Workflow که timeout میشه

اگه npm run build یا composer install طولانی شد، timeout-minutes رو ببر بالا. پیش‌فرض ۳۶۰ دقیقه برای hosted runner کافیه، ولی خودت بذار ۱۵-۲۰ دقیقه تا اگه واقعاً هنگ کرد، fail بشه.

سوالات متداول

آیا GitHub Actions برای پروژه‌های Private هم رایگانه؟

بله، تا ۲۰۰۰ دقیقه در ماه. برای اکثر پروژه‌های شخصی و کوچیک کافیه. اگه بیشتر بخوای، پلن Team ($۴/ماه) ۳۰۰۰ دقیقه میده.

چطور چندتا سرور (مثلاً staging + production) رو همزمان دیپلوی کنم؟

از environment استفاده کن. دو تا job تعریف کن، یکی برای staging یکی برای prod. هر کدوم secrets و environment مخصوص خودشون رو دارن. می‌تونی approval هم اضافه کنی که prod فقط با تأیید دستی دیپلوی بشه.

آیا میشه بدون SSH دیپلوی کرد؟

بله. سه راه: ۱) Self-hosted runner (مستقیم روی سرور)، ۲) Git pull از طریق HTTPS + deploy key روی سرور، ۳) Docker registry (push image، server pull می‌کنه). ولی برای اکثر پروژه‌ها SSH ساده‌ترین و امن‌ترین راهه.

اگه سرور down شد وسط deploy چی میشه؟

Workflow fail میشه و notification میاد. چون symlink هنوز به release قدیمی اشاره می‌کنه، سایت همون نسخه قبلی رو نشون میده. وقتی سرور برگشت، workflow رو دوباره Run کن.

آیا میشه قبل از production، روی staging تست کرد؟

بله. دو تا job بساز: اول test و lint (روی pull request)، بعد deploy به staging (خودکار با merge به develop)، و در نهایت deploy به production (با approval manual). GitHub Environments برای این الگو عالیه.

آیا باید node_modules رو commit کنم؟

نه. .gitignore باید node_modules و vendor رو داشته باشه. تو workflow با npm ci و composer install تولیدشون می‌کنی. تنها استثنا monorepoها با cache strategy خاصه.

جمع‌بندی + چک‌لیست نهایی

اگه امروز می‌خوای شروع کنی، این ۸ قدم رو برو:

  1. یه کاربر deploy روی سرور بساز (نه root)
  2. SSH key اختصاصی ed25519 بساز، passphrase نذار
  3. کلید خصوصی رو بذار تو GitHub Secrets
  4. .github/workflows/deploy.yml رو با الگوی مرحله ۴ بساز
  5. اولین deploy رو با workflow_dispatch دستی اجرا کن (نه push، تا کنترل داشته باشی)
  6. اگه جواب داد، branch protection rule بذار که فقط PR merge بشه نه مستقیم push
  7. Health check و Slack/Telegram notification اضافه کن
  8. اسکریپت rollback رو آماده کن و یه بار تو staging تست کن

یه قانون طلایی: اولین deploy production رو آخر هفته انجام نده. صبح روز کاری وقتی همه هستن. اگه خراب شد، کسی باشه درست کنه.

قدم بعدی: اگه می‌خوای عمیق‌تر بری، مقاله راهنمای سئو تکنیکال لاراول رو بخون — مخصوصاً بخش canonical و hreflang که تو پروژه‌های دوزبانه لازمه.

موفق باشی. اگه سوالی داشتی یا گیر کردی، تو کامنت‌ها بپرس.

Captain Code
verified
نویسنده

Captain Code

مشاهده پروفایل arrow_back

عاشق دنیای کدنویسی و به اشتراک‌گذاری دانش. ما در کاپیتان کد تلاش می‌کنیم بهترین محتوا را برای شما تولید کنیم.

forum

نظرات

lock_person

برای ثبت نظر وارد شوید

برای شرکت در گفتگو، لطفاً ابتدا وارد حساب کاربری‌تان شوید.

login ورود
chat_bubble_outline

هنوز نظری ثبت نشده

اولین نفری باشید که نظر خود را درباره این مطلب به اشتراک می‌گذارد!