Hugo + Caddy 静态博客与自建 DERP 部署指南

使用 Docker Compose 托管 Caddy 与自建 DERP,通过 GitHub Actions 构建 Hugo、原子发布静态文件,并整理本地搜索、快捷键与安全维护实践。

本文可公开复用。域名、服务器路径、用户名、仓库、指纹和凭据均为示例;真实 Token、私钥、证书私钥与公网地址不得写入仓库、日志或文章。

一、最终架构

服务示例地址端口职责
Caddy 静态站点blog.example.comTCP 80、443HTTPS、压缩与静态文件
DERPderp.example.comTCP 4443Tailscale 中继
STUNderp.example.comUDP 3478NAT 穿透测试
SSH服务器地址TCP 22运维与 Actions 部署

Caddy 与 DERP 不争用 443:Caddy 接管标准 HTTPS,DERP 使用 4443。Hugo 只在 CI 构建,服务器只托管产物,运行面更小。

二、Compose 管理 Caddy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
services:
  caddy:
    image: caddy:2-alpine
    ports: ["80:80", "443:443"]
    volumes:
      - ./site:/srv:ro
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped
volumes: { caddy_data: {}, caddy_config: {} }
1
2
3
4
5
blog.example.com, www.blog.example.com {
    root * /srv
    encode gzip zstd
    file_server
}

Caddy 证书位于 Docker 命名卷,不在站点目录。日常检查:

1
2
3
4
cd <博客目录>
docker compose ps
docker compose logs -f caddy
curl -I https://blog.example.com/

三、DERP 与 Tailscale

DERP 使用域名和自签名证书时,证书 SAN 必须包含该域名;Tailscale 策略中的节点应使用域名、DERPPort: 4443、STUNPort: 3478,并同步证书的 CertName 指纹。

1
{"HostName":"derp.example.com","DERPPort":4443,"STUNPort":3478,"CertName":"sha256-raw:<证书指纹>"}

保留官方区域(OmitDefaultRegions: false)作为故障回退。浏览器访问 http://derp.example.com:4443/ 出现“HTTP request to an HTTPS server”是正常现象;请用 TLS 工具或 tailscale netcheck 验证。

四、GitHub Actions:可验证构建与原子发布

工作流固定 Hugo extended 版本,直接从 Hugo 官方 Release 下载二进制和 checksums 文件,再用 sha256sum --check --strict 校验。这样不依赖维护较慢的第三方 Hugo 安装 Action。

发布流程:

1
2
3
push main → checkout → 固定子模块提交 → 官方 Hugo Release + SHA-256 校验
→ 构建 public/ → Artifact → SSH 上传单个压缩包
→ 服务器解压到 staging → 验证 index.html → 原子切换 site → 重建 Caddy

失败时旧站点保留;部署脚本会在 Caddy 重建失败时尝试回滚。工作流以并发组串行化生产部署,避免多个发布互相覆盖。

Secrets 仅保存服务器地址、端口、部署用户、部署目录、Compose 目录和部署私钥。主题子模块优先直连 GitHub,失败时才使用可选 GH_PROXY 变量。

五、部署账户与更新任务

Actions 使用专用部署用户和专用 SSH 密钥:服务器仅保存公钥;该用户禁用密码与交互式认证。Token、私钥与证书私钥权限应为最小化,例如 root:root、600,并置于仓库外。

Hugo 更新检查可由 cron 每日执行:无稳定版更新只写日志;有更新才触发 workflow dispatch。只有构建和部署成功后才发送通知,避免误报。

六、主题、可访问性与浏览体验

主题已迁移 Hugo 弃用 API:locale、.Site.Language.Locale 与 hugo.Data,生产构建不再出现弃用警告。文章链接使用稳定 slug。

当前体验优化:

  • 本地 Lunr 搜索生成 /search.json;中文使用字符级分词,不依赖外部搜索服务;
  • Alt+S 聚焦搜索框,不自动搜索;Alt+X 浏览文章/标题,Alt+Z 浏览导航;
  • 首页返回时恢复上次文章焦点;
  • 代码块、引用块和文章链接提供复制功能;
  • 文章入口按配置在新标签页/窗口打开,并使用 rel="noopener";
  • 评论按需加载,减少初始第三方请求;关闭 instant.page;图片缩放固定版本而非 @latest;
  • 装饰元素不制造屏幕阅读器噪音,功能控件使用原生语义和明确名称。

七、维护清单

1
2
3
hugo --environment production --minify --destination $env:TEMP\blog-check
git status
git push origin main
1
2
3
tailscale netcheck
docker compose ps
docker compose logs -f caddy
  1. 修改主题后先检查 Hugo 弃用警告和生产构建;
  2. 主题通用功能放主题仓库,站点专属设置留在站点配置;
  3. 不要保留与主题完全相同的本地模板覆盖;
  4. 更新镜像、Hugo 或脚本后先验证,再发布;
  5. 定期轮换 Token 和部署密钥,并检查安全组只开放必要端口。
updatedupdated2026-10-052026-10-05