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

h1从零到一：GitHub Actions + Caddy 全自动部署静态网站实战p本教程将完整复现一个现代化静态网站从本地开发到自动化部署的全流程。你将搭建一套 strong“Git推送即发布” 的自动化系统，无需手动操作服务器。教程基于 strongNuxt.js 静态生成，但核心流程适用于任何静态网站（如VitePress、Next.js SSG、Hugo等）。blockquotepstrong最终效果：本地 codegit push → 自动构建、测试 → 安全同步至云服务器 → 网站即刻更新（HTTPS自动启用）。hrh2🎯 系统架构与核心理念p这套方案的核心是 strong“声明式自动化”：你只需在代码仓库中声明“做什么”（配置文件），GitHub Actions 和 Caddy 就会自动执行“怎么做”。ptextp开发者本地 (Local) ↓ spangit push GitHub 仓库 (Repository) ↓ span触发 GitHub Actions (CI/CD 管道) ↓ span构建、测试、同步阿里云服务器 (Alibaba Cloud ECS) ↓ spanCaddy 提供 HTTPS 服务用户访问 (HTTPS Website)h2📦 前置准备ollistrong一个 GitHub 仓库listrong一台阿里云 ECS 实例（或任何具有公网 IP 的 Linux 服务器）ulli推荐系统：Ubuntu 22.04 / Alibaba Cloud Linux 3li安全组预先开放：strongSSH(22)、strongHTTP(80)、strongHTTPS(443) 端口listrong一个域名（可选，但推荐。教程以 codeexample.com 为例）hrh2🚀 第一部分：服务器初始化h31.1 登录并安装基础软件p通过 SSH 登录你的云服务器。precodespanspan# 更新系统包 spanspansudospan aptspan updatespan &&span sudospan aptspan upgradespan -y spanspan spanspan# 安装 Caddy（现代化的 Web 服务器，自动 HTTPS） spanspansudospan aptspan installspan caddy spanspan spanspan# 安装 Node.js 环境 spanspancurlspan -fsSLspan https://deb.nodesource.com/setup_24.xspan |span sudospan -Espan bashspan - spanspansudospan aptspan installspan -yspan nodejs spanspan spanspan# 创建网站根目录并授权 spanspansudospan mkdirspan -pspan /var/www/my-site spanspan# 注意：请务必将 `caddy` 用户替换为你的实际用户名 spanspansudospan chownspan -Rspan caddy:caddyspan /var/www/my-site spanspansudospan chmodspan -Rspan 755span /var/www/my-site h31.2 配置 Caddyp编辑 Caddy 的主配置文件，告诉它如何服务你的网站。precodespanspansudospan nanospan /etc/caddy/Caddyfile p粘贴以下配置，strong请务必将 codeexample.com 替换为你的真实域名。如果没有域名，可以用 codehttp://你的服务器IP 格式，但将无法享受自动 HTTPS。precodespanspan# /etc/caddy/Caddyfile spanspanexample.com, www.example.com { spanspan # 网站根目录（必须与后续自动化部署的目录一致） spanspan root * /var/www/my-site spanspan # 启用静态文件服务器 spanspan file_server spanspan # 对单页应用(SPA)至关重要：使前端路由正常工作 spanspan try_files {path} /index.html spanspan # 启用压缩 spanspan encode gzip zstd spanspan} p保存退出后（在 nano 中：codeCtrl+X，然后 codeY，再 codeEnter），重启 Caddy 使配置生效。precodespanspan# 检查配置语法 spanspansudospan caddyspan validatespan --configspan /etc/caddy/Caddyfile spanspan# 重启服务 spanspansudospan systemctlspan restartspan caddy spanspan# 检查服务状态 spanspansudospan systemctlspan statusspan caddy blockquotepstrong💡 提示：看到 codeactive (running) 状态即表示 Caddy 已就绪。如果使用域名，Caddy 会在首次访问时strong自动申请并配置 Let‘s Encrypt SSL 证书。hrh2🔐 第二部分：配置 SSH 密钥对与 GitHub Secretsp自动化部署的核心是让 GitHub Actions 能安全地连接到你的服务器。我们使用 SSH 密钥对进行认证。h32.1 在本地生成 SSH 密钥对p在你的strong本地电脑（而非服务器）上执行：precodespanspan# 生成一对新的密钥，专用于自动化部署 spanspanssh-keygenspan -tspan ed25519span -fspan ~/.ssh/id_github_actionsspan -Nspan "" spanspan spanspan这将生成两个文件： spanspan spanspan-span **span私钥span**span (span`span~/.ssh/id_github_actionsspan`span)：**绝密**，相当于你的“钥匙”。 spanspan spanspan-span **span公钥span**span (span`span~/.ssh/id_github_actions.pubspan`span)：可以公开，相当于“锁芯”。 h32.2 将公钥部署到服务器p未命名olli复制公钥内容：precodespanspancatspan ~/.ssh/id_github_actions.pub listrong登录你的云服务器，将公钥添加到授权列表：precodespanspan# 将上一步复制的公钥内容，粘贴到引号内，然后执行整条命令 spanspanechospan 'span你的公钥内容span'span >>span ~/.ssh/authorized_keys spanspan# 设置正确的权限（非常重要！） spanspanchmodspan 600span ~/.ssh/authorized_keys spanspanchmodspan 700span ~/.ssh h32.3 将私钥配置为 GitHub Secretsolli查看私钥内容：precodespanspancatspan ~/.ssh/id_github_actions li进入你的 GitHub 仓库，点击 strongSettings → strongSecrets and variables → strongActions。li点击 strongNew repository secret，添加以下三个密钥：ullistrongcodeSERVER_HOST：你的云服务器strong公网 IP 地址。listrongcodeSERVER_USER：用于 SSH 登录的用户名（例如 coderoot、codeubuntu）。listrongcodeSSH_PRIVATE_KEY：粘贴你刚刚复制的strong完整私钥内容（包括 code-----BEGIN OPENSSH PRIVATE KEY----- 和 code-----END OPENSSH PRIVATE KEY----- 行）。hrh2⚙️ 第三部分：创建 GitHub Actions 工作流p这是自动化的“大脑”。在你的项目根目录创建文件：code.github/workflows/deploy.ymlprecodespanspannamespan:span Deploy to Production spanspan spanspanonspan: spanspan pushspan: spanspan branchesspan:span [span mainspan ]span # 仅在推送到 main 分支时触发 spanspan spanspanjobsspan: spanspan build-and-deployspan: spanspan runs-onspan:span ubuntu-latest spanspan spanspan stepsspan: spanspan # 1. 拉取代码 spanspan -span namespan:span Checkout code spanspan usesspan:span actions/checkout@v4 spanspan spanspan # 2. 设置 Node.js 环境 (以 Nuxt 项目为例) spanspan -span namespan:span Setup Node.js spanspan usesspan:span actions/setup-node@v4 spanspan withspan: spanspan node-versionspan:span 'span24span'span # 使用你项目所需的 Node 版本 spanspan cachespan:span 'spanpnpmspan'span # 如果用 npm 则改为 'npm' spanspan spanspan # 3. 安装依赖 spanspan -span namespan:span Install dependencies spanspan runspan:span pnpm install spanspan spanspan # 4. 打包文件 spanspan -span namespan:span Build spanspan runspan:span pnpm run buildspan # 这会生成 .output 目录 spanspan spanspan # 4. 将构建产物同步到云服务器 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/public/span # Nuxt 静态文件输出目录 spanspan remote_pathspan:span /var/www/my-site/span # 服务器目标目录，必须与 Caddyfile 中配置一致 spanspan remote_hostspan:span ${{ secrets.SERVER_HOST }} spanspan remote_userspan:span ${{ secrets.SERVER_USER }} spanspan remote_keyspan:span ${{ secrets.SSH_PRIVATE_KEY }} spanspan spanspan*span*关键配置说明**： spanspan spanspan-span `spanpath`span:span 你的静态网站构建输出目录。对于其他框架： spanspan spanspan -span VitePressspan:span `spandocs/.vitepress/dist/` spanspan spanspan -span Next.js (SSG)span:span `spanout/` spanspan spanspan -span Vue CLIspan:span `spandist/` spanspan spanspan -span Hugospan:span `spanpublic/` spanspan spanspan-span `spanremote_path`span:span 必须与服务器上 Caddy 配置的 `root` 目录完全一致。 spanspan spanspan-span `spanswitchesspan:span --delete`span:span 确保服务器上的文件是构建结果的精确镜像，自动删除多余文件。 hrh2🧪 第四部分：触发首次部署与验证h34.1 提交并推送代码p将工作流配置文件添加到 Git 并推送到仓库，触发首次自动化部署。precodespanspangitspan addspan .github/workflows/deploy.yml spanspangitspan commitspan -mspan “feat:span 添加自动化部署工作流” spanspangitspan pushspan originspan main h34.2 监控部署过程olli进入你的 GitHub 仓库，点击 strongActions 标签页。li你会看到名为 “Deploy to Production” 的工作流正在运行。li点击进入，可以实时查看每个步骤的日志。li当所有步骤显示绿色对勾（✅），表示部署成功。h34.3 验证网站ulli打开浏览器，访问你的域名（如 codehttps://example.com）。li如果使用 IP，访问 codehttp://你的服务器IP。li你应该能看到部署的网站，并且地址栏显示strong安全的 HTTPS 锁标（如果使用了域名）。hrh2🔧 第五部分：高级配置与问题排查h35.1 处理静态资源（图标、图片）p确保项目的strong静态资源（如 codefavicon.ico、图片）放在正确的目录：ullistrongNuxt 3/4: code/public/ 目录listrongNuxt 2: code/static/ 目录li构建后，这些文件会自动复制到 code.output/public/ 下。pstrong推荐做法：在 codepublic/ 下创建子目录（如 codepublic/icons/, codepublic/images/）进行分类管理。h35.2 关键问题排查清单tabletheadtrth现象th可能原因th解决方案tbodytrtdstrongActions 日志卡在 SSH 连接td1. SSH 密钥格式错误 br2. 安全组未开放 22 端口 br3. 服务器 codesshd_config 配置限制td1. 检查私钥格式，确保在 GitHub Secrets 中完整、多行 br2. 检查阿里云安全组入方向规则 br3. 检查服务器 code/etc/ssh/sshd_config 中的 codePermitRootLogin 和 codeAllowUsers 设置trtdstrong网站可以 HTTP 访问，但 HTTPS 报错td1. 域名 DNS 解析未生效或错误 br2. 安全组未开放 443 端口td1. 运行 codenslookup yourdomain.com 检查 DNS 解析 br2. 检查安全组 443 端口规则trtdstrong网站显示 “404 Not Found”td1. Caddy coderoot 目录配置错误 br2. 文件未成功同步到服务器td1. 核对 code/etc/caddy/Caddyfile 中的 coderoot 路径与 codedeploy.yml 中的 coderemote_path br2. 登录服务器检查 code/var/www/my-site/ 目录下是否有文件trtdstrong构建失败 (Lint/Type Error)td代码检查或类型错误td1. 本地运行 codepnpm run lint 和 codepnpm run typecheck 修复错误 br2. 或暂时在 codedeploy.yml 中注释掉相关检查步骤h35.3 查看服务器日志p当遇到问题时，服务器日志是寻找线索的黄金位置。precodespanspan# 查看 Caddy 实时日志 spanspansudospan journalctlspan -uspan caddyspan -f spanspan spanspan# 查看 SSH 认证日志（排查连接问题） spanspansudospan tailspan -fspan /var/log/auth.log hrh2📈 总结：你现在拥有了什么？p通过本教程，你已成功搭建了一套strong完全自动化、可追溯、可回滚的现代静态网站部署流水线：ollistrong自动化：只需 codegit push，后续所有流程自动完成。listrong零配置 HTTPS：Caddy 自动管理 SSL 证书的申请和续期。listrong环境一致：每次构建都在全新的 GitHub 运行器中进行，杜绝“在我机器上好好的”问题。listrong安全可靠：基于 SSH 密钥认证，密钥安全存储在 GitHub Secrets。listrong可回滚：如需回滚，只需在 Git 中检出旧版本并推送，Actions 会自动将服务器文件同步至旧状态。pstrong从此，你可以专注于本地开发，将构建、测试、发布的重复劳动全部交给自动化流程。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 pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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}