Skip to content

Deploying the docs site (Cloudflare Pages)

The docs site (this site) is built with Zensical and hosted on Cloudflare Pages. Static HTML, free tier, instant cache invalidation.

Local preview

cd docs-site
uvx zensical serve

Opens a hot-reloading dev server at http://localhost:8000/. Save any markdown file → page reloads. Change zensical.toml → restart.

Local build

cd docs-site
uvx zensical build

Produces a static site under docs-site/site/. Open docs-site/site/index.html in your browser to verify.

First-time Cloudflare Pages setup

Done once per project; you can skip this section if the project already has a Pages site.

  1. Sign in to the Cloudflare dashboard.
  2. Workers & Pages → Create application → Pages → Connect to Git.
  3. Pick the GitHub repo (you'll authorise the Cloudflare GitHub app the first time).
  4. Configure build:
  5. Production branch: main
  6. Build command: pip install zensical && cd docs-site && zensical build
  7. Build output directory: docs-site/site
  8. Root directory (advanced): leave as the repo root.
  9. Environment variables (if needed):
  10. PYTHON_VERSION = 3.12 (Cloudflare's default Python is older; pin if you hit issues).
  11. Save and Deploy.

The first build runs immediately. Subsequent deploys happen automatically on every push to main. Preview deploys happen on every PR — Cloudflare leaves a comment on the PR with the preview URL.

Custom domain

If you've configured docs.heymaud.com:

  1. Pages site → Custom domains → Set up a custom domain.
  2. Enter docs.heymaud.com.
  3. Cloudflare adds the CNAME for you (because the domain is on Cloudflare DNS already). If it isn't, follow the on-screen DNS instructions.
  4. Update the Cloudflare Access application to the new hostname — see Access protection. Skip this and the site goes public.

Access protection

The docs site is not public. It sits behind a Cloudflare Access self-hosted application (Zero Trust → Access → Applications → Maud docs) that challenges every visitor for a one-time email PIN and only admits the addresses listed in its Allow policy. Nothing in this repo enforces that — it lives entirely in the Cloudflare dashboard.

An Access application matches on hostname. That is the trap: when the product was renamed and the docs moved to docs.heymaud.com, the existing application kept guarding the old hostname and the new one shipped with no protection at all. Nothing failed, nothing alerted; it was noticed by hand on 2026-09-13 and a new application created the same day.

So, whenever the docs hostname changes, updating the Access application's destination is part of the same change — not a follow-up. Add the new hostname to the app before flipping DNS, and remove the old one afterwards.

Checking it is still on

curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://docs.heymaud.com/

Healthy is a 302 to a *.cloudflareaccess.com/cdn-cgi/access/login/... URL. A 200 means the site is open to the world — go and fix it before anything else.

If it has lapsed

Zero Trust → Access → Applications → Add an application → Self-hosted:

  1. Name: Maud docs. Destination: subdomain docs, domain heymaud.com, path blank.
  2. Policy: action Allow, include rule Emails → the addresses that should get in. Session duration: 1 month.
  3. Save. It is live immediately — no redeploy needed. Re-run the curl above.

PR preview deploys on *.pages.dev are on Cloudflare's domain, not ours, so they are not covered by this application. Don't put anything in the docs you would mind being on a preview URL.

What the docs may not contain

Access is a second line of defence, not a reason to relax the first: no credentials, keys, or default passwords in the docs — ever. Reference the secret by name and say where it lives (Fly secrets, the password manager). The one time the docs held a default password was also the window the site was open.

Workflow

The convenient cycle:

  1. Edit a markdown file in docs-site/docs/.
  2. uvx zensical serve to preview.
  3. Commit + push to a feature branch.
  4. Open a PR → Cloudflare leaves a preview-deploy comment within ~30 seconds.
  5. Merge → production updates within ~30 seconds.

When the build breaks

  • 404 on a nav link — usually a typo in zensical.toml's nav table. Compare against the directory tree.
  • pymdownx.something not found — the pip install zensical step is incomplete; pin the Zensical version in the build command if you hit version drift.
  • Missing image / asset — paths in markdown are relative to the markdown file's location. Move or fix the path.

Reverting

Cloudflare keeps every deploy. Pages site → Deployments → Rollback restores any previous build with one click.