从零到一：GitHub Actions + Caddy 全自动部署动态网站实战

h1从零到一：GitHub Actions + Caddy 全自动部署动态网站实战p本教程将指导你如何为动态网站搭建一套完整的自动化部署系统，实现 strong“代码推送即发布” 的 DevOps 流程。我们将使用 strongNode.js + Express 作为示例后端，但整体架构与配置方法同样适用于 Python（Django/Flask）、Java（Spring Boot）、PHP（Laravel）等动态网站技术栈。blockquotepstrong最终效果：本地 codegit push → 自动测试 → 构建 → 部署至云服务器 → 服务热重启 → 网站即刻更新（HTTPS 自动启用）。hrh2🎯 系统架构与核心理念p与静态网站不同，动态网站部署不仅需要同步文件，还需要：olli安装运行时环境li安装项目依赖li可能需要数据库迁移li重启应用进程（如 PM2、systemd 服务）li配置反向代理（Caddy 作为反向代理到后端端口）p整个流程依然基于 strong声明式自动化，通过 GitHub Actions 实现端到端无人值守部署。precodetext 开发者本地 (Local) ↓ [git push] GitHub 仓库 (Repository) ↓ [触发] GitHub Actions (CI/CD 管道) ↓ [构建、测试、同步、远程执行命令] 阿里云服务器 (Alibaba Cloud ECS) ↓ [Caddy 反向代理 + HTTPS] 用户访问 (HTTPS Dynamic Website) hrh2📦 前置准备ollistrong一个 GitHub 仓库（包含你的动态网站源码）listrong一台云服务器（阿里云 ECS、腾讯云 CVM 等均可） ulli推荐系统：Ubuntu 22.04 / Alibaba Cloud Linux 3li安全组开放：strongSSH(22)、strongHTTP(80)、strongHTTPS(443)、strong应用端口（如 3000）listrong一个域名（可选但推荐，用于 HTTPS）listrong安装好 Node.js 运行时（如果使用其他语言，请提前安装对应环境）hrh2🚀 第一部分：服务器初始化h31.1 登录并安装基础软件precodespanspan# 更新系统 spanspansudospan aptspan updatespan &&span sudospan aptspan upgradespan -y spanspan spanspan# 安装 Node.js（示例使用 Node 20） spanspancurlspan -fsSLspan https://deb.nodesource.com/setup_24.xspan |span sudospan -Espan bashspan - spanspansudospan aptspan installspan -yspan nodejs spanspan spanspan# 安装 PM2（进程管理，用于保持应用运行） spanspansudospan npmspan installspan -gspan pm2 spanspan spanspan# 安装 Caddy（作为反向代理和 HTTPS 终端） spanspansudospan aptspan installspan caddy h31.2 创建应用目录并授权precodespanspan# 创建应用目录 spanspansudospan mkdirspan -pspan /var/www/my-dynamic-app spanspan# 注意：请务必将 `caddy` 用户替换为你的实际用户名 spanspansudospan chownspan -Rspan caddy:caddyspan /var/www/my-site spanspansudospan chmodspan -Rspan 755span /var/www/my-dynamic-app h31.3 配置 Caddy 作为反向代理p假设你的 Node.js 应用运行在 code127.0.0.1:3000。precodespanspansudospan nanospan /etc/caddy/Caddyfile p内容如下（替换 codeexample.com 为你的域名）：precodespanspanexample.com, www.example.com { spanspan # 反向代理到本地的 Node.js 应用 spanspan reverse_proxy 127.0.0.1:3000 spanspan # 启用压缩 spanspan encode gzip zstd spanspan} p保存并验证配置：precodespanspansudospan caddyspan validatespan --configspan /etc/caddy/Caddyfile spanspansudospan systemctlspan restartspan caddy spanspansudospan systemctlspan statusspan caddy hrh2🔐 第二部分：配置 SSH 密钥对与 GitHub Secretsp与静态网站相同，我们需要让 GitHub Actions 能通过 SSH 连接到服务器并执行命令。h32.1 在本地生成 SSH 密钥对precodespanspanssh-keygenspan -tspan ed25519span -fspan ~/.ssh/id_github_actions_dynamicspan -Nspan "" h32.2 将公钥部署到服务器olli复制公钥：precodespanspancatspan ~/.ssh/id_github_actions_dynamic.pub li登录服务器，添加到 codeauthorized_keys：precodespanspanechospan 'span你的公钥内容span'span >>span ~/.ssh/authorized_keys spanspanchmodspan 600span ~/.ssh/authorized_keys spanspanchmodspan 700span ~/.ssh h32.3 在 GitHub 仓库中添加 Secretsp进入仓库 strongSettings → Secrets and variables → Actions，添加：ullicodeSERVER_HOST：服务器公网 IPlicodeSERVER_USER：登录用户名（如 codeubuntu）licodeSSH_PRIVATE_KEY：完整的私钥内容hrh2⚙️ 第三部分：创建 GitHub Actions 工作流p在项目根目录创建 code.github/workflows/deploy.yml：precodespanspannamespan:span Deploy Dynamic App to Production spanspan spanspanonspan: spanspan pushspan: spanspan branchesspan:span [span mainspan ] spanspan spanspanjobsspan: spanspan cispan: spanspan runs-onspan:span ${{ matrix.os }} spanspan spanspan strategyspan: spanspan matrixspan: spanspan osspan:span [spanubuntu-latestspan] spanspan nodespan:span [span24span] spanspan spanspan stepsspan: spanspan -span namespan:span Checkout spanspan usesspan:span actions/checkout@v6 spanspan spanspan -span namespan:span Install pnpm spanspan usesspan:span pnpm/action-setup@v4 spanspan spanspan -span namespan:span Install node spanspan usesspan:span actions/setup-node@v6 spanspan withspan: spanspan node-versionspan:span ${{ matrix.node }} spanspan cachespan:span pnpm spanspan spanspan -span namespan:span Install dependencies spanspan runspan:span pnpm install spanspan spanspan -span namespan:span Lint spanspan runspan:span pnpm run lint spanspan spanspan -span namespan:span Build spanspan runspan:span pnpm run build spanspan spanspan -span namespan:span Deploy to Server via Rsync spanspan usesspan:span burnett01/rsync-deployments@7.0.1 spanspan withspan: spanspan switchesspan:span -avz --deletespan # 递归、压缩、同步删除 spanspan pathspan:span .output/span # 👈 Nuxt 的文件输出目录 spanspan remote_pathspan:span /var/www/my-site/span # 👈 服务器上的目标目录 spanspan remote_hostspan:span ${{ secrets.SERVER_HOST }}span # 服务器 IP spanspan remote_userspan:span ${{ secrets.SERVER_USER }}span # 登录用户名 spanspan remote_keyspan:span ${{ secrets.SSH_PRIVATE_KEY }}span # SSH 私钥 spanspan spanspan -span namespan:span Start/Reload Node.js service spanspan usesspan:span appleboy/ssh-action@v1.0.0 spanspan withspan: spanspan hostspan:span ${{ secrets.SERVER_HOST }} spanspan usernamespan:span ${{ secrets.SERVER_USER }} spanspan keyspan:span ${{ secrets.SSH_PRIVATE_KEY }} spanspan scriptspan:span | spanspan echo "🚀 启动 Node.js SSR 服务..." spanspan cd /var/www/my-site spanspan spanspan # 启动或重启 Nuxt 应用 spanspan if pm2 describe nuxt-app > /dev/null 2>&1; then spanspan echo "🔄 重启现有应用..." spanspan pm2 reload nuxt-app spanspan else spanspan echo "🚀 启动新应用..." spanspan pm2 start server/index.mjs --name "nuxt-app" \ spanspan --log /var/log/nuxt-app.log \ spanspan --error /var/log/nuxt-app-error.log \ spanspan --output /var/log/nuxt-app-out.log \ spanspan --time spanspan spanspan # 保存 PM2 配置以便开机自启 spanspan pm2 save spanspan fi spanspan spanspan echo "✅ 服务启动完成" spanspan pm2 status nuxt-app blockquotepstrong说明：ulli我们使用 codeappleboy/ssh-action 在服务器上直接执行命令li步骤包括：拉取代码、安装生产依赖、重启 PM2 进程li如果是其他语言，替换 codenpm ci 和 codepm2 命令即可（如 codepip install -r requirements.txt + codesystemctl restart myapp）hrh2🧪 第四部分：触发首次部署与验证h34.1 推送代码precodespanspangitspan addspan .github/workflows/deploy.yml spanspangitspan commitspan -mspan “feat:span 添加动态网站自动化部署” spanspangitspan pushspan originspan main h34.2 监控部署过程olli进入 GitHub 仓库的 strongActions 标签页li查看运行日志，确认每一步是否成功li如果失败，检查日志中的错误信息（常见于依赖安装、权限或进程启动）h34.3 验证服务ulli访问 codehttps://你的域名li或 codehttp://服务器IP:服务器端口li检查应用是否正常运行，并确认 HTTPS 是否生效hrh2🔧 第五部分：高级配置与问题排查h35.1 环境变量管理p动态网站通常需要环境变量（如数据库连接串、API 密钥）。建议在服务器上创建 code.env 文件，并在部署流程中保持不变。precodespanspan# 在服务器上 spanspancdspan /var/www/my-dynamic-app spanspannanospan .env spanspan# 填入你的环境变量 p在 PM2 或 systemd 配置中加载该文件。h35.2 数据库迁移自动化p如果你的项目使用数据库迁移工具（如 Sequelize、Alembic、Laravel Migrate），可以在部署脚本中加入迁移命令：precodespanspan-span namespan:span Deploy to Server via SSH spanspan usesspan:span appleboy/ssh-action@v1.0.3 spanspan withspan: spanspan ... spanspan scriptspan:span | spanspan cd /var/www/my-dynamic-app spanspan git pull origin main spanspan npm ci --only=production spanspan npx sequelize db:migrate # 迁移数据库 spanspan pm2 restart my-app h35.3 关键问题排查清单tabletheadtrth现象th可能原因th解决方案tbodytrtdstrong应用启动失败td1. 环境变量缺失 br2. 端口被占用 br3. 依赖安装失败td1. 检查 code.env 文件 br2. 使用 codenetstat -tlnp 查看端口占用 br3. 查看 codenpm ci 或 codepip install 日志trtdstrongCaddy 返回 502 Bad Gatewaytd1. 后端应用未运行 br2. Caddy 配置中端口错误td1. 用 codepm2 list 或 codesystemctl status 检查应用进程 br2. 核对 Caddyfile 中的 codereverse_proxy 端口trtdstrong数据库连接失败td1. 数据库服务未启动 br2. 迁移脚本出错 br3. 凭证错误td1. 重启数据库服务 br2. 检查迁移日志 br3. 核对连接字符串trtdstrong部署过程中 SSH 执行失败td1. 脚本权限不足 br2. 路径错误 br3. 命令不存在td1. 使用 codesudo 或在脚本前加 codecd /正确路径 br2. 使用 codewhich pm2 确认命令路径h35.4 查看日志precodespanspan# 查看应用日志（PM2） spanspanpm2span logsspan my-app spanspan spanspan# 查看 Caddy 日志 spanspansudospan journalctlspan -uspan caddyspan -f spanspan spanspan# 查看系统日志 spanspansudospan tailspan -fspan /var/log/syslog hrh2📈 总结：动态网站自动化部署的核心价值p通过本教程，你已经搭建了一套strong全自动、可监控、易回滚的动态网站部署系统，具备以下优势：ollistrong一键部署：从代码推送到服务上线，全程自动化listrong进程守护：通过 PM2 保持应用持续运行，崩溃自动重启listrongHTTPS 自动管理：Caddy 自动处理 SSL 证书申请与续期listrong环境一致性：依赖在每次部署时重新安装，避免环境漂移listrong快速回滚：如需回滚，只需 codegit revert 并推送，Actions 会自动执行旧版本部署pstrong从此，你可以专注于业务开发，将构建、测试、部署、运维的复杂性交给自动化系统处理。hrp如果你使用的是 strongDocker，还可以进一步容器化部署，实现更高程度的环境一致性。欢迎基于本教程继续探索更先进的部署模式。stylehtml pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}