Static Delivery for a Zero-Idle Research Site
CloudFront + S3 origin, deploy-by-pivom, and a pure-Hugo build
Abstract
research.prosyon.ca is served as a static site from S3 behind CloudFront, with no always-on compute and no per-request cost floor. This paper documents the delivery model end to end: how the bucket and distribution are provisioned, how the CI pipeline resolves the correct deployment target at publish time, and why the build was reduced to a single dependency-free Hugo invocation.
Problem and context
A research library is read far more often than it is written, and it may sit untouched for weeks between publications. Paying for idle compute to serve a handful of HTML documents is exactly the kind of standing cost this project exists to avoid. The delivery model therefore has one hard requirement and two soft ones:
- Hard: cost trends to $0 when no one is reading.
- Soft: publishing is a single, reproducible CI step.
- Soft: the build has as few moving parts as possible.
codex theme) and the data-bearing services are documented
separately.The model
The site is a classic static-origin CDN topology: an S3 bucket holds the rendered output, and a CloudFront distribution terminates TLS, caches at the edge, and reaches the bucket through an Origin Access Control so the bucket is never public.1
The whole topology is one reusable Terraform module, instantiated per site:
us-east-1 (see WP-003)ALIAS/ANAME to the distributionWhy not a server
A small VM or container would be simpler to reason about, but it violates the hard requirement: it bills whether or not anyone reads. Static origins invert that — you pay for bytes stored and bytes served, both of which go to zero at idle.2
The approach: deploy-by-pivom
The interesting part is publishing. The CI pipeline does not hard-code the bucket name. Instead it resolves the deployment target at run time from the site’s domain, a pattern referred to internally as deploy-by-pivom:
- Terraform provisions (or confirms) the bucket and distribution.
- A resolver step maps
research.prosyon.ca→ the concrete bucket name and exports it to the job environment. - The build artifact is uploaded to that bucket.
Decoupling the publish step from a literal bucket name means the same workflow publishes any domain the resolver knows about, and renaming or recreating the bucket never requires editing the pipeline.
| Stage | Action | Guarantee |
|---|---|---|
deploy | terraform apply | Infra exists and matches code |
| resolve | domain → bucket name | Upload targets the right origin |
build | hugo --minify | Deterministic, dependency-free out |
| publish | sync artifact to S3 | Edge serves the new revision |
Implementation: a pure-Hugo build
The original placeholder build shelled out to an “under construction” action.
Replacing it, the design choice that mattered most was eliminating Node from
the build. The codex theme is written in vanilla CSS and uses Hugo Pipes
for concatenation, minification, and fingerprinting3 — so there is no
Tailwind step, no npm install, and no package.json to keep in sync.
The build collapses to one hermetic command:
# Render content/ + themes/codex into www/public/
hugo --source www --minify --gc
That single binary invocation honors the build/release/run separation cleanly: the build stage produces an immutable artifact, and the release stage only moves bytes.4
Cache and invalidation
CloudFront caches aggressively at the edge. Because CSS and JS assets are fingerprinted (their filename contains a content hash), a new build emits new asset URLs and the old ones simply age out — no asset invalidation needed. Only HTML documents require a targeted invalidation on publish.
Trade-offs
- No server-side anything. Search, comments, and dynamic features must be client-side or delegated to a separate service. For a document library this is acceptable, even desirable.
- External DNS. Apex records point at CloudFront via
ALIAS/ANAME; the certificate flow this implies is its own subject (WP-003). - Cold author experience. Publishing requires a CI round-trip rather than a live edit. The reproducibility is worth the latency.
Summary
Static origin, edge cache, private bucket, and a one-command build give a site that is cheap to run, boring to operate, and impossible to leave in a half-deployed state. The only standing cost is storage measured in megabytes.
References
- (2024). Restricting access to an Amazon S3 origin (Origin Access Control). Amazon CloudFront Developer Guide.
- (2017). The Twelve-Factor App — Build, release, run. 12factor.net.
- (2025). Hugo Pipes: asset processing. gohugo.io documentation.
Cite this paper
Jon (2026). Static Delivery for a Zero-Idle Research Site (v1.0). Prosyon Research. https://research.prosyon.ca/papers/static-delivery-pipeline/