Skip to content

ADR-0003 — Site hosting on Cloudflare Pages

  • Status: Accepted
  • Date: 2026-07-26

Context

swarmplane.dev is registered at Cloudflare, with the zone on Cloudflare nameservers (hugh.ns.cloudflare.com, nelly.ns.cloudflare.com).

The site is a static MkDocs build (site/, ~18 pages, ~3 MB). It needs a host, HTTPS on an apex domain, and as little deploy ceremony as possible — the site should never be the reason a piece of writing does not ship.

An earlier note in project planning favoured Firebase Hosting on the grounds of "narrative consistency": a GCP-native project whose own site runs on GCP.

Decision

Host the site on Cloudflare Pages, project swarmplane, deployed by direct upload from the local site/ build.

uv run mkdocs build --strict
wrangler pages deploy site --project-name swarmplane --branch main

Custom domains: swarmplane.dev and www.swarmplane.dev, both served directly via proxied CNAMEs in the zone:

swarmplane.dev      CNAME  swarmplane.pages.dev   Proxied  Auto
www.swarmplane.dev  CNAME  swarmplane.pages.dev   Proxied  Auto

Rationale, including a retraction

The narrative-consistency argument was wrong and is withdrawn. No reader checks where documentation is hosted. The product targets Cloud Run and AlloyDB; the docs site is marketing collateral, and hosting it elsewhere costs nothing in credibility. Choosing infrastructure for the story it tells rather than the job it does is the kind of reasoning this project's philosophy pages argue against.

What actually decides it:

  • Registrar, DNS and hosting in one account. No cross-provider DNS configuration.
  • No provisioning friction. Firebase behind Cloudflare DNS requires records set to DNS-only (grey cloud) during certificate issuance, plus a separate GCP project to create and manage.
  • Already authenticated. wrangler is logged in; other sites in the same account (loopkernel.com, reasonata.com) already use this exact direct-upload-plus-apex-domain pattern, so it is a known-good path rather than a new one.
  • Free tier, automatic HTTPS, global CDN.

Consequences

Positive — one-command deploys; no DNS coordination; the build stays local so mkdocs build --strict gates every publish.

Negative — deploys are manual. Nothing publishes on git push, so the site can drift behind the repository. Mitigation is either discipline or a later move to a Git-connected Pages project, which would require granting Cloudflare access to the private repo.

Operational note: attach custom domains via the dashboard, not the API

Recorded because it cost three steps instead of one, and the obvious assumption is wrong.

A custom domain attached to a Pages project through the Cloudflare API does not get the dashboard's automatic DNS setup, even when the zone lives in the same account. The Custom domains tab then displays generic third-party-DNS instructions ("Log into your DNS provider, add the following CNAME record") with no create-it-for-me button, and both domains sit at Verifying indefinitely because nothing ever creates the record.

Two contributing facts:

  • The wrangler OAuth token carries pages (write) and zone (read). It can attach a domain to the project, but cannot create DNS records — dns_records is a separate scope, and even listing records returns an authentication error.
  • wrangler has no pages domain subcommand at 4.107.0. Project and deployment management only.

Do this instead: add the custom domain from the dashboard (Workers & Pages → project → Custom domains → Set up a custom domain). The dashboard holds the permissions to create the DNS record itself, making it one step.

Two further observations from the first setup:

  • Universal SSL was already issued for the zone (certificate notBefore predated the deploy by four days), so HTTPS worked the instant the CNAME existed — there was no certificate wait. Registering the domain at Cloudflare pre-warms this.
  • The Pages API reported both domains as pending well after they were serving HTTP 200 with a valid certificate. Cloudflare's verification state lags its own data plane. Verify with curl against the hostname, not with the API status field.

Alternatives rejected

Firebase Hosting — see the retraction above. Would also have added a GCP project whose only purpose is serving static files.

Git-connected Cloudflare Pages — builds on push, which solves the drift problem. Needs either a public repo or Cloudflare access to a private one, plus a remote build configuration for uv and MkDocs. Worth revisiting once the repo goes public; deferred rather than rejected.

Netlify / GitHub Pages — both workable. Neither has the advantage of already holding the registrar and DNS.