اگه یکخط جواب میخوای: 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 خاصه.
جمعبندی + چکلیست نهایی
اگه امروز میخوای شروع کنی، این ۸ قدم رو برو:
- یه کاربر
deployروی سرور بساز (نه root) - SSH key اختصاصی ed25519 بساز، passphrase نذار
- کلید خصوصی رو بذار تو GitHub Secrets
.github/workflows/deploy.ymlرو با الگوی مرحله ۴ بساز- اولین deploy رو با
workflow_dispatchدستی اجرا کن (نه push، تا کنترل داشته باشی) - اگه جواب داد، branch protection rule بذار که فقط PR merge بشه نه مستقیم push
- Health check و Slack/Telegram notification اضافه کن
- اسکریپت rollback رو آماده کن و یه بار تو staging تست کن
یه قانون طلایی: اولین deploy production رو آخر هفته انجام نده. صبح روز کاری وقتی همه هستن. اگه خراب شد، کسی باشه درست کنه.
قدم بعدی: اگه میخوای عمیقتر بری، مقاله راهنمای سئو تکنیکال لاراول رو بخون — مخصوصاً بخش canonical و hreflang که تو پروژههای دوزبانه لازمه.
موفق باشی. اگه سوالی داشتی یا گیر کردی، تو کامنتها بپرس.