从零到一:GitHub Actions + Caddy 全自动部署动态网站实战
本教程将指导你如何为动态网站搭建一套完整的自动化部署系统,实现 “代码推送即发布” 的 DevOps 流程。我们将使用 Node.js + Express 作为示例后端,但整体架构与配置方法同样适用于 Python(Django/Flask)、Java(Spring Boot)、PHP(Laravel)等动态网站技术栈。
最终效果:本地
git push→ 自动测试 → 构建 → 部署至云服务器 → 服务热重启 → 网站即刻更新(HTTPS 自动启用)。
🎯 系统架构与核心理念
与静态网站不同,动态网站部署不仅需要同步文件,还需要:
- 安装运行时环境
- 安装项目依赖
- 可能需要数据库迁移
- 重启应用进程(如 PM2、systemd 服务)
- 配置反向代理(Caddy 作为反向代理到后端端口)
整个流程依然基于 声明式自动化,通过 GitHub Actions 实现端到端无人值守部署。
text
开发者本地 (Local)
↓ [git push]
GitHub 仓库 (Repository)
↓ [触发]
GitHub Actions (CI/CD 管道)
↓ [构建、测试、同步、远程执行命令]
阿里云服务器 (Alibaba Cloud ECS)
↓ [Caddy 反向代理 + HTTPS]
用户访问 (HTTPS Dynamic Website)
📦 前置准备
- 一个 GitHub 仓库(包含你的动态网站源码)
- 一台云服务器(阿里云 ECS、腾讯云 CVM 等均可)
- 推荐系统:Ubuntu 22.04 / Alibaba Cloud Linux 3
- 安全组开放:SSH(22)、HTTP(80)、HTTPS(443)、应用端口(如 3000)
- 一个域名(可选但推荐,用于 HTTPS)
- 安装好 Node.js 运行时(如果使用其他语言,请提前安装对应环境)
🚀 第一部分:服务器初始化
1.1 登录并安装基础软件
# 更新系统
sudo apt update && sudo apt upgrade -y
# 安装 Node.js(示例使用 Node 20)
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
# 安装 PM2(进程管理,用于保持应用运行)
sudo npm install -g pm2
# 安装 Caddy(作为反向代理和 HTTPS 终端)
sudo apt install caddy
1.2 创建应用目录并授权
# 创建应用目录
sudo mkdir -p /var/www/my-dynamic-app
# 注意:请务必将 `caddy` 用户替换为你的实际用户名
sudo chown -R caddy:caddy /var/www/my-site
sudo chmod -R 755 /var/www/my-dynamic-app
1.3 配置 Caddy 作为反向代理
假设你的 Node.js 应用运行在 127.0.0.1:3000。
sudo nano /etc/caddy/Caddyfile
内容如下(替换 example.com 为你的域名):
example.com, www.example.com {
# 反向代理到本地的 Node.js 应用
reverse_proxy 127.0.0.1:3000
# 启用压缩
encode gzip zstd
}
保存并验证配置:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl status caddy
🔐 第二部分:配置 SSH 密钥对与 GitHub Secrets
与静态网站相同,我们需要让 GitHub Actions 能通过 SSH 连接到服务器并执行命令。
2.1 在本地生成 SSH 密钥对
ssh-keygen -t ed25519 -f ~/.ssh/id_github_actions_dynamic -N ""
2.2 将公钥部署到服务器
- 复制公钥:
cat ~/.ssh/id_github_actions_dynamic.pub - 登录服务器,添加到
authorized_keys:echo '你的公钥内容' >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys chmod 700 ~/.ssh
2.3 在 GitHub 仓库中添加 Secrets
进入仓库 Settings → Secrets and variables → Actions,添加:
SERVER_HOST:服务器公网 IPSERVER_USER:登录用户名(如ubuntu)SSH_PRIVATE_KEY:完整的私钥内容
⚙️ 第三部分:创建 GitHub Actions 工作流
在项目根目录创建 .github/workflows/deploy.yml:
name: Deploy Dynamic App to Production
on:
push:
branches: [ main ]
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [24]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install node
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Lint
run: pnpm run lint
- name: Build
run: pnpm run build
- name: Deploy to Server via Rsync
uses: burnett01/rsync-deployments@7.0.1
with:
switches: -avz --delete # 递归、压缩、同步删除
path: .output/ # 👈 Nuxt 的文件输出目录
remote_path: /var/www/my-site/ # 👈 服务器上的目标目录
remote_host: ${{ secrets.SERVER_HOST }} # 服务器 IP
remote_user: ${{ secrets.SERVER_USER }} # 登录用户名
remote_key: ${{ secrets.SSH_PRIVATE_KEY }} # SSH 私钥
- name: Start/Reload Node.js service
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
echo "🚀 启动 Node.js SSR 服务..."
cd /var/www/my-site
# 启动或重启 Nuxt 应用
if pm2 describe nuxt-app > /dev/null 2>&1; then
echo "🔄 重启现有应用..."
pm2 reload nuxt-app
else
echo "🚀 启动新应用..."
pm2 start server/index.mjs --name "nuxt-app" \
--log /var/log/nuxt-app.log \
--error /var/log/nuxt-app-error.log \
--output /var/log/nuxt-app-out.log \
--time
# 保存 PM2 配置以便开机自启
pm2 save
fi
echo "✅ 服务启动完成"
pm2 status nuxt-app
说明:
- 我们使用
appleboy/ssh-action在服务器上直接执行命令- 步骤包括:拉取代码、安装生产依赖、重启 PM2 进程
- 如果是其他语言,替换
npm ci和pm2命令即可(如pip install -r requirements.txt+systemctl restart myapp)
🧪 第四部分:触发首次部署与验证
4.1 推送代码
git add .github/workflows/deploy.yml
git commit -m “feat: 添加动态网站自动化部署”
git push origin main
4.2 监控部署过程
- 进入 GitHub 仓库的 Actions 标签页
- 查看运行日志,确认每一步是否成功
- 如果失败,检查日志中的错误信息(常见于依赖安装、权限或进程启动)
4.3 验证服务
- 访问
https://你的域名 - 或
http://服务器IP:服务器端口 - 检查应用是否正常运行,并确认 HTTPS 是否生效
🔧 第五部分:高级配置与问题排查
5.1 环境变量管理
动态网站通常需要环境变量(如数据库连接串、API 密钥)。建议在服务器上创建 .env 文件,并在部署流程中保持不变。
# 在服务器上
cd /var/www/my-dynamic-app
nano .env
# 填入你的环境变量
在 PM2 或 systemd 配置中加载该文件。
5.2 数据库迁移自动化
如果你的项目使用数据库迁移工具(如 Sequelize、Alembic、Laravel Migrate),可以在部署脚本中加入迁移命令:
- name: Deploy to Server via SSH
uses: appleboy/ssh-action@v1.0.3
with:
...
script: |
cd /var/www/my-dynamic-app
git pull origin main
npm ci --only=production
npx sequelize db:migrate # 迁移数据库
pm2 restart my-app
5.3 关键问题排查清单
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 应用启动失败 | 1. 环境变量缺失 2. 端口被占用 3. 依赖安装失败 | 1. 检查 .env 文件 2. 使用 netstat -tlnp 查看端口占用 3. 查看 npm ci 或 pip install 日志 |
| Caddy 返回 502 Bad Gateway | 1. 后端应用未运行 2. Caddy 配置中端口错误 | 1. 用 pm2 list 或 systemctl status 检查应用进程 2. 核对 Caddyfile 中的 reverse_proxy 端口 |
| 数据库连接失败 | 1. 数据库服务未启动 2. 迁移脚本出错 3. 凭证错误 | 1. 重启数据库服务 2. 检查迁移日志 3. 核对连接字符串 |
| 部署过程中 SSH 执行失败 | 1. 脚本权限不足 2. 路径错误 3. 命令不存在 | 1. 使用 sudo 或在脚本前加 cd /正确路径 2. 使用 which pm2 确认命令路径 |
5.4 查看日志
# 查看应用日志(PM2)
pm2 logs my-app
# 查看 Caddy 日志
sudo journalctl -u caddy -f
# 查看系统日志
sudo tail -f /var/log/syslog
📈 总结:动态网站自动化部署的核心价值
通过本教程,你已经搭建了一套全自动、可监控、易回滚的动态网站部署系统,具备以下优势:
- 一键部署:从代码推送到服务上线,全程自动化
- 进程守护:通过 PM2 保持应用持续运行,崩溃自动重启
- HTTPS 自动管理:Caddy 自动处理 SSL 证书申请与续期
- 环境一致性:依赖在每次部署时重新安装,避免环境漂移
- 快速回滚:如需回滚,只需
git revert并推送,Actions 会自动执行旧版本部署
从此,你可以专注于业务开发,将构建、测试、部署、运维的复杂性交给自动化系统处理。
如果你使用的是 Docker,还可以进一步容器化部署,实现更高程度的环境一致性。欢迎基于本教程继续探索更先进的部署模式。
如果这篇文档对你有帮助,可以请我喝杯咖啡 ☕️


评论区
登录后参与评论