构建阶段,页面源码与配置进入生成器及依赖;生成页面与复制的 static/ 文件组成 public/。请求阶段,浏览器向 Caddy 发送 HTTPS 请求,Caddy 读取选中的发布树。CSP 由浏览器执行,请求路径没有应用服务器或数据库。
预期架构:构建工具在服务前执行,Caddy 仍是 Web 服务器。浏览器 CSP 是另一项策略。只发布生成文件;服务根并不是符号链接沙箱。

Context

本站是文档与运维内容:用来阅读、检索、归档的页面。动态应用服务器可以渲染模板、管理会话或提供交互组件,但这些是可选能力,不是当前需求。内容本身对服务器端状态、按用户的认证逻辑或客户端渲染都没有要求。

Decision

用 Caddy 将一个预构建的静态树作为站点提供服务,通过受控的发布切换选择一个不可变发布目录。目录发布 ADR 解释了切换方式及其跨请求限制。边缘后面没有应用服务器、没有脚本通道、没有数据库。

浏览器 ──HTTPS──► Caddy ──文件系统读取──► public/ 发布树

预期的浏览器无脚本契约由响应头(script-src 'none')下发,并由拒绝可执行脚本与残缺产物的构建检查支持。CSP 由浏览器执行,不能证明没有应用服务器。构建输出是 public/,其中包括生成的页面和从源 static/ 目录复制的文件。

后果

  • 发布更简单: 内容产物没有应用状态或数据库 schema 要迁移。发布切换、缓存行为与回滚仍须验证。
  • 威胁面更窄: 请求路径没有应用模板求值器或数据库查询层。Caddy 和浏览器仍会解析不可信输入,生成器与依赖仍在构建时执行。
  • 缓存可检查: 不可变发布文件有稳定字节。校验器、压缩变体与新鲜度指令仍需正确服务器配置。
  • 接受的取舍: 没有脚本驱动的交互、认证个性化或每用户 HTML。原生文档交互仍然可用。若确实需要,应作为独立的服务来组合,而不是并进这棵树。

仍然存在的边界

只发布生成树,不发布仓库根、构建凭据或私有内容。Caddy 的 root 不是文件系统沙箱:根内的符号链接可能暴露根外目标。发布前应检查产物符号链接、权限与错误路由。无脚本 CSP 不会让 HTML 注入无害,也不能保护被公开的机密文件。

持续更新服务器,验证实际公共响应,并保留经过测试的上一版。这些运维责任不会随应用层的移除而消失。仓库记录的是预期架构与示例配置,不是证明当前生产主机没有其他服务的资产清单。

为什么它胜过替代方案

动态层带来一个运行时、一个状态边界和一条更慢的编辑路径。对价值在文档本身的内容,这笔成本买来了内容用不到的能力。静态服务没有牺牲站点需要的任何东西,同时移除了只会让发布与安全边界变复杂的部分。

来源