Context

The site is documentation and operations content: pages to be read, searched, and archived. Early brainstorming considered a dynamic application server to render templates, manage sessions, or serve interactive components. The content itself imposes no requirement for server-side state, authenticated per-user logic, or client-side rendering.

Decision

Serve the site as a pre-built static tree from Caddy, a single directory that is swapped as a whole on release. No application server, scripting lane, or database lives behind the edge.

Caddy (public edge) ──HTTPS──► static/ tree (assets, HTML, SVGs)

The served contract is enforced by headers (script-src 'none') and by build-time checks that reject scripts and broken artifacts.

Consequences

  • Releases are boring: publishing is a complete-directory swap, so there is no state migration and no schema.
  • The threat surface is narrow: no runtime executes user input, closing whole injection classes.
  • Caching is honest: every file has a stable byte identity and a validator.
  • Trade-offs accepted: no interactivity, no authenticated personalization, no per-user HTML. Where that is genuinely needed, it would compose behind a separate service rather than be folded into this tree.

Why this beats the alternative

A dynamic tier adds a runtime, a state boundary, and a slower path to edit. For content whose value is in the document itself, that cost buys capabilities the content does not use. Static serving gives away nothing the site needs and removes the parts that would only complicate releases and the security boundary.

Sources