In the build-time area, source pages and configuration feed the generator and dependencies. Generated pages and copied static files form public/. At request time a browser sends HTTPS requests to Caddy, which reads the selected release tree. CSP is enforced in the browser. The request path has no application server or database.
Intended architecture: build tools execute before serving; Caddy remains the web server. Browser CSP is separate. Publish only generated files: the root is not a symlink sandbox.

Context

The site is documentation and operations content: pages to be read, searched, and archived. A dynamic application server could render templates, manage sessions, or serve interactive components, but those are capabilities rather than current requirements. 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, an immutable release directory selected through a controlled publication switch. The directory-release ADR explains the switch and its cross-request limits. No application server, scripting lane, or database lives behind the edge.

Browser ──HTTPS──► Caddy ──filesystem reads──► public/ release tree

The intended no-script browser contract is delivered through headers (script-src 'none') and supported by build-time checks that reject executable scripts and broken artifacts. CSP is enforced by the browser; it does not prove the absence of an application server. The build output is public/, which includes generated pages and files copied from the source static/ directory.

Consequences

  • Releases are simpler: the content artifact has no application state or database schema to migrate. Publication, cache behavior, and rollback still need verification.
  • The threat surface is narrower: there is no application template evaluator or database query layer on the request path. Caddy and the browser still parse untrusted input, and the generator and dependencies still execute during builds.
  • Caching is inspectable: immutable release files have stable bytes. Validators, compression variants, and freshness directives still need correct server configuration.
  • Trade-offs accepted: no script-driven interactivity, authenticated personalization, or per-user HTML. Native document interactions remain available. Where that is genuinely needed, it would compose behind a separate service rather than be folded into this tree.

Boundaries that remain

Publish only the generated tree, not the repository root, build credentials, or private content. Caddy’s root is not a filesystem sandbox: a symlink inside it can expose a target outside that root. Review artifact symlinks, permissions, and error routes before publication. A no-script CSP does not make HTML injection harmless or protect an exposed secret file.

Keep the server patched, validate the actual public response, and retain a tested previous release. These are continuing operational responsibilities, even though the application tier was removed. The repository documents the intended architecture and sample configuration; it is not an inventory proving the present production host has no other services.

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