GitHub Actions 部署策略:从 VPS 到云平台的 CD 流水线
导语
凌晨三点,我盯着 GitHub Actions 的日志界面,红色错误一行行往上滚。“Host key verification failed”。又是 SSH 问题。
这已经是第五次部署失败了。明明本地测试全通过,推到 GitHub 后就炸。那一刻我特别想骂人——但也意识到一件事:部署策略的选择,远比我想的复杂。
不管是自己管理的 VPS,还是 Vercel、Cloudflare Pages 这类托管平台,每种方案都有坑。选错了,深夜调试的次数只会越来越多。
这篇文章聊聊 GitHub Actions 的几种部署策略,帮你找到适合自己的那条路。
VPS SSH 部署:老派但可靠
说实话,我一开始特别抗拒 VPS 部署。觉得太麻烦——SSH 密钥、known_hosts、rsync 参数……一堆东西要搞。
但踩过几次坑后,我发现这套”老派”方案反而最可控。
SSH 密钥配置:别硬编码
最常见的问题就是 SSH 密钥放哪儿。
新手往往直接把私钥写进 workflow 文件。大错特错。GitHub Secrets 才是正确位置。
去仓库的 Settings → Secrets → Actions,添加 SSH_PRIVATE_KEY。然后在 workflow 里这样用:
- name: Setup SSH
uses: webfactory/ssh-agent-action@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
这个 action 会自动启动 ssh-agent,加载你的密钥。省心。
known_hosts:避开 “Host key verification failed”
SSH 第一次连接服务器时,会问你是否信任这个 host。交互式问答在 CI 环境里没法处理,所以需要提前把服务器指纹加到 known_hosts。
两种方式:
方式一:用 action 自动添加
- name: Add server to known hosts
uses: webfactory/ssh-agent-action@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
SSH_KNOWN_HOSTS 的内容可以这样获取:
ssh-keyscan -H your-server.com >> known_hosts.txt
# 把文件内容复制到 GitHub Secrets
方式二:手动配置
- name: Add server to known hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
方式一更干净,方式二适合快速调试。
rsync 还是 scp?
部署文件传输,我用 rsync。原因很简单:
- 只传改动的文件,节省时间
- 可以排除特定目录(比如 node_modules)
- 支持增量同步
一个典型的 rsync 命令:
- name: Deploy to server
run: |
rsync -avz --delete \
--exclude 'node_modules' \
--exclude '.git' \
./dist/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/var/www/html/
--delete 参数会删除目标目录里源目录没有的文件。小心用——搞错了可能删掉不该删的东西。
部署后命令:重启服务
静态网站传完就完事了。但如果部署的是 Node.js 应用,还得重启服务。
我喜欢用 PM2 管理 Node 进程。部署后执行:
- name: Restart application
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"cd /var/www/app && pm2 restart all"
或者更保险的方式——重启特定应用:
- name: Restart application
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"pm2 restart my-app --update-env"
--update-env 会重新加载环境变量,适合配置有变化的情况。
云平台部署:托管服务的便利
VPS 部署的问题是——你得自己管服务器。安全补丁、SSL 证书续期、防火墙规则……琐事一堆。
托管平台就省心多了。推送代码,自动构建,自动部署。你只管写代码。
Vercel:前端项目的首选
Vercel 对前端项目的支持几乎是完美的。Next.js、Astro、React——一键部署,零配置。
但如果你的项目需要后端 API,就要注意了。Vercel 的 Serverless Functions 有运行时间限制(免费版 10 秒,Pro 版 60 秒)。超过就会 timeout。
对于纯静态站点或者简单的 API,Vercel 很够用。复杂后端服务还是得靠自己。
GitHub Actions 部署到 Vercel 的配置:
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Vercel CLI
run: npm i -g vercel@latest
- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy Project Artifacts to Vercel
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
VERCEL_TOKEN 从 Vercel 控制台生成,存到 GitHub Secrets。
Cloudflare Pages:免费额度大方
Cloudflare Pages 的免费额度比 Vercel 大方得多。带宽不限,构建次数每月 500 次——对个人项目绰绰有余。
而且 Cloudflare 的全球 CDN 真的快。我自己测过,亚洲访问速度比 Vercel 稳定。
部署配置:
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm run build
- name: Deploy
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: my-project
directory: dist
Cloudflare 还有个好处——R2 存储免费额度也大。静态资源可以放 R2,配合 Pages 的 CDN,加载速度能快不少。
Netlify:老牌稳定选手
Netlify 我用得不如前两个多,但它是老牌托管平台,生态成熟。
部署配置类似:
- name: Deploy to Netlify
uses: netlify/actions/cli@master
with:
args: deploy --prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
Netlify 的 Form handling 功能挺实用——表单提交自动处理,适合简单的营销页面。
托管平台的限制
话说回来,托管平台也不是万能的。
几个常见限制:
- 构建环境受限:内存、CPU 都有上限,大型项目可能构建失败
- 自定义程度低:想改 nginx 配置?没门
- 依赖平台存活:平台倒闭或者改政策,你得迁移
- 国内访问问题:部分平台在国内访问不稳定(虽然 Cloudflare 有改善)
如果你的项目需要完全控制,还是得回到 VPS。
混合策略:灵活与控制并存
很多项目不是”纯静态”也不是”纯后端”。前端是 Next.js,后端还要接数据库、跑定时任务……
这种情况下,混合部署可能是最优解。
静态页面托管 + API 部署到 VPS
一个典型架构:
- 静态页面(HTML/CSS/JS)部署到 Cloudflare Pages 或 Vercel
- Node.js API 服务部署到自己的 VPS
- 数据库也在 VPS 上(或者用 Supabase/PlanetScale 托管)
好处是各取所长:
- 前端享受 CDN 加速和自动 HTTPS
- 后端有完全控制权,不受托管平台限制
- 数据库访问延迟低(API 和数据库在同一台机器)
GitHub Actions 的多阶段部署
用一个 workflow 同时部署到两个地方:
name: Hybrid Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-path: ./dist
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist
deploy-frontend:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: build-output
path: dist
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: my-frontend
directory: dist
deploy-backend:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup SSH
uses: webfactory/ssh-agent-action@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Deploy API to VPS
run: |
rsync -avz --delete \
--exclude 'node_modules' \
./api/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/var/www/api/
- name: Restart API service
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"cd /var/www/api && npm install && pm2 restart api"
这个 workflow 有三个 job:
build:构建项目,产出静态文件deploy-frontend:把静态文件部署到 Cloudflare Pagesdeploy-backend:把 API 部署到 VPS 并重启服务
needs: build 确保部署 job 在构建完成后才执行。upload-artifact 和 download-artifact 在 job 之间传递构建产物。
环境变量分离
混合部署的一个挑战:前端和后端的环境变量不一样。
前端需要知道 API 地址,后端需要知道数据库密码。
我的做法:
# 前端 job
- name: Set frontend env
run: |
echo "API_URL=https://api.mydomain.com" >> $GITHUB_ENV
# 后端 job
- name: Deploy with env
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"cd /var/www/api && pm2 restart api --update-env DATABASE_URL=${{ secrets.DATABASE_URL }}"
敏感信息(数据库密码、API token)永远走 GitHub Secrets。非敏感信息(API 地址)可以写在 workflow 里。
实战配置示例
下面是一个完整的 VPS 部署 workflow,覆盖了前面提到的所有要点。
完整 workflow 文件
name: Deploy to VPS
on:
push:
branches: [main]
workflow_dispatch: # 手动触发部署
env:
NODE_VERSION: '20'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
retention-days: 1
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Setup SSH
uses: webfactory/ssh-agent-action@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Add server to known hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
- name: Deploy files
run: |
rsync -avz --delete \
--exclude '.htaccess' \
./dist/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:${{ secrets.DEPLOY_PATH }}
- name: Verify deployment
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} \
"ls -la ${{ secrets.DEPLOY_PATH }}"
- name: Send deployment notification
if: always()
run: |
curl -X POST "${{ secrets.NOTIFICATION_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d '{"text": "Deployment completed: ${GITHUB_SHA}"}'
需要配置的 Secrets
| Secret 名称 | 说明 | 获取方式 |
|---|---|---|
SSH_PRIVATE_KEY | SSH 私钥内容 | 本地生成,公钥放服务器 |
SERVER_HOST | 服务器 IP 或域名 | 你的 VPS 信息 |
SERVER_USER | SSH 登录用户名 | 通常是 root 或 ubuntu |
DEPLOY_PATH | 部署目标路径 | 如 /var/www/html |
NOTIFICATION_WEBHOOK | 部署通知地址 | Slack/Telegram webhook |
常见问题排查
部署失败时,看日志很容易晕——信息太多。
我的排查顺序:
- SSH 连接问题:看 “Setup SSH” 和 “Add server to known hosts” 步骤
- 如果失败,检查密钥格式、known_hosts 内容
- rsync 传输问题:看 “Deploy files” 步骤
- 如果失败,检查路径是否存在、权限是否正确
- 服务重启问题:看 “Verify deployment” 步骤
- 如果失败,检查目标路径是否有文件
一个技巧:在失败的步骤后面加调试输出。
- name: Debug SSH connection
if: failure()
run: |
echo "SSH config:"
cat ~/.ssh/config || echo "No config file"
echo "Known hosts:"
cat ~/.ssh/known_hosts || echo "No known_hosts file"
ssh -v ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} echo "Connection test"
ssh -v 会输出详细日志,能看到问题在哪。
总结
写了这么多,其实就一句话:没有完美的部署方案,只有最适合你项目的方案。
选型建议:
- 纯静态站点(博客、文档):Cloudflare Pages 或 Vercel,省心
- 简单 API + 前端:托管平台够了,别折腾 VPS
- 复杂后端 + 数据库:VPS 或云服务器,控制权重要
- 混合架构:前端托管 + 后端 VPS,各取所长
不管选哪种,GitHub Actions 的配置模式都差不多:构建 → 传输 → 重启。把这三步拆清楚,调试时就不会乱了方向。
还有一点:部署失败时别慌。日志分段看,先定位是 SSH 连接问题还是命令执行问题。加个调试步骤,问题很快就暴露出来了。
下次凌晨三点部署失败,希望你能更快找到原因。
配置 GitHub Actions VPS 部署
完整配置 GitHub Actions 通过 SSH 部署到 VPS 的流程
⏱️ 预计耗时: 30 分钟
- 1
步骤1: 生成 SSH 密钥对
在本地生成专用于部署的 SSH 密钥:
• ssh-keygen -t ed25519 -C "deploy@github" -f deploy_key
• 公钥(deploy_key.pub)添加到服务器 ~/.ssh/authorized_keys
• 私钥(deploy_key)内容存入 GitHub Secrets 的 SSH_PRIVATE_KEY - 2
步骤2: 配置 GitHub Secrets
在仓库 Settings → Secrets → Actions 添加:
• SSH_PRIVATE_KEY:私钥完整内容
• SERVER_HOST:服务器 IP 或域名
• SERVER_USER:SSH 用户名(如 root 或 ubuntu)
• DEPLOY_PATH:目标部署路径 - 3
步骤3: 创建 Workflow 文件
在 .github/workflows/deploy.yml 创建部署配置:
• 添加 SSH 密钥配置步骤(webfactory/ssh-agent-action)
• 配置 known_hosts 避免 host verification 失败
• 使用 rsync 传输构建产物
• 部署后执行服务重启命令 - 4
步骤4: 测试部署流程
推送代码触发自动部署,或手动触发:
• 观察每个步骤的日志输出
• SSH 失败时检查密钥格式和 known_hosts
• rsync 失败时检查路径和权限
• 添加调试步骤排查问题
常见问题
GitHub Actions 部署时出现 'Host key verification failed' 怎么解决?
• 方案一:使用 ssh-keyscan 获取服务器指纹,存入 SSH_KNOWN_HOSTS Secret
• 方案二:在 workflow 中手动执行 ssh-keyscan -H $SERVER_IP >> ~/.ssh/known_hosts
推荐方案一,更干净安全。
SSH 密钥应该放在哪里?直接写在 workflow 文件里行吗?
Vercel、Cloudflare Pages、Netlify 哪个更适合个人项目?
纯静态站点优先选 Cloudflare Pages。
混合部署架构有什么优势?
用 GitHub Actions 的多 job workflow 可以同时部署到两个地方。
部署失败时日志太多看不清楚,怎么快速定位问题?
1. SSH 连接问题 → 检查 Setup SSH 和 known_hosts 步骤
2. rsync 传输问题 → 检查路径存在性和权限
3. 服务重启问题 → 检查目标路径文件列表
在失败步骤后加调试输出(ssh -v 详细日志)能快速暴露问题。
rsync 的 --delete 参数有什么风险?
建议首次部署不加 --delete,确认正确后再启用。或者用 --delete-excluded 只删除被排除的文件。
8 分钟阅读 · 发布于: 2026年4月7日 · 修改于: 2026年4月8日
相关文章
GitHub Actions Matrix 矩阵构建:多版本并行测试实战
GitHub Actions Matrix 矩阵构建:多版本并行测试实战
Supabase Auth 实战:邮箱验证、OAuth 与会话管理
Supabase Auth 实战:邮箱验证、OAuth 与会话管理
GitHub Actions 缓存策略:加速 CI/CD 流水线 5 倍

评论
使用 GitHub 账号登录后即可评论