项目背景
个人技术站点不需要为了“现代化”长期运行 Node.js、数据库和容器编排。对于 2 vCPU、1 GB 内存级别的 VPS,更合适的方案是让 CI 完成构建,服务器只保存并提供静态文件。
这份案例描述本站的目标基础设施。当前仓库只包含网站源码,不代表示例域名、远程服务器和自动部署已经实际配置。
目标与约束
- 生产环境没有 Node.js 常驻进程;
- Caddy 负责 HTTPS、压缩与静态文件服务;
- SELinux 保持 Enforcing,firewalld 保持启用;
- 自动部署使用非 root 账户;
- 现有代理服务可能占用 UDP/443,部署前必须确认端口;
- 站点可以随时通过 Git 仓库重新构建。
技术架构
Git Repository
│ push main
▼
GitHub Actions
│ npm ci / astro build
▼
dist/
│ rsync over SSH
▼
Rocky Linux /var/www/blog
│
▼
Caddy
│ HTTPS
▼
Browser
构建产物和源代码有清晰边界。服务器上的 /var/www/blog 是可替换产物,Markdown、配置和构建逻辑只存在于版本库。
端口与现有服务
部署前先检查 TCP/80、TCP/443 和 UDP/443,而不是直接启动 Caddy:
sudo ss -lntup | grep -E ':(80|443)\b'
如果只有 UDP/443 被现有代理使用,可以让 Caddy 监听 TCP/80 和 TCP/443,并禁用 HTTP/3:
{
servers :443 {
protocols h1 h2
}
}
如果 TCP/443 已被占用,则需要由一个明确的入口完成 SNI 或反向代理路由,不能让两个普通进程竞争同一监听地址。
Web 与安全边界
生产目录使用 /var/www/blog,Caddy 只读取静态文件。目录权限授予部署用户写入、Caddy 读取,不让自动部署账户拥有无关 sudo 权限。
SELinux 使用持久化文件上下文,而不是临时关闭:
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/blog(/.*)?"
sudo restorecon -Rv /var/www/blog
firewalld 只开放实际需要的服务。SSH 管理入口优先限制来源或通过 Tailscale 访问。
发布流程
自动化之前先手工跑通一次完整链路:
- 本地执行
npm ci和npm run build; - 将
dist/上传到临时目录; - 同步到
/var/www/blog; - 使用
curl -I检查 HTTPS 与缓存响应; - 确认 Caddy 日志、文件权限与 SELinux 没有异常。
只有手工链路稳定后,才把同样步骤放进 CI。这样自动化失败时仍然知道每一层应该如何独立验证。
可恢复性
站点真正的源数据是 Git 仓库。服务器损坏时,新主机只需要恢复 Caddy 配置、安全策略和部署密钥,然后重新构建并同步 dist/。node_modules 和服务器构建缓存都不属于备份对象。
项目复盘
低维护并不等于忽略安全,而是让组件数量与需求匹配。静态输出、最小权限和可重建部署将故障面缩小到 DNS、端口、TLS、文件权限和 Web Server 几个可观察层级。
真实上线前,需要替换示例域名,核对服务器端口占用,并在实际环境分别验证 firewalld、SELinux 与现有代理服务。