Skip to content

ADR 006: Docs on Cloudflare Pages, not laptop tunnel

Status: accepted Date: 2026-05-04 Supersedes: —

Context

Earlier in the session we'd put the MkDocs Material docs site behind the same Cloudflare Tunnel as the warehouse UI, routed by path (/docs/*localhost:8766). It worked, but felt wrong:

  1. Static content was tied to laptop uptime. Docs are markdown → HTML; there's no reason they should disappear when the Mac sleeps.
  2. Adding a launchd KeepAlive job for mkdocs serve doesn't actually fix laptop-dependence — KeepAlive only restarts on crash, not when the Mac is closed. "24/7" is a lie when the underlying machine isn't.
  3. A cloudflared_watcher.sh + fswatch script existed only to fix a problem (cloudflared not hot-reloading config.yml) that I'd caused by editing config.yml without restarting cloudflared — band-aid for a one-time mistake, not infrastructure.
  4. ADR 002's principle was "use the least-laptop-dependent tier you can, given the data dependencies." The docs have NO data dependencies — they're pure markdown. Putting them on the laptop was the wrong tier.

Decision

Static docs go to Cloudflare Pages, deployed via GitHub Actions on push to main. Custom domain TBD (currently casey-system-docs.pages.dev; can move to docs.caseymanos.com via dashboard when needed).

The warehouse UI + cloudflared tunnel stay on the laptop, started manually via kbui — both have hard dependencies on the laptop (local DuckDB, exposing localhost) and there's no benefit to faking always-on for them.

What this implies for each piece

Piece Tier Always-on? Why
Docs site Cloudflare Pages yes (edge) Static, no data dep
OTQCheckinAgent Cloudflare Worker yes (edge) Already there per ADR 002
Warehouse UI Laptop, kbui only when laptop awake Hard data dep on local DuckDB
Tunnel Laptop, kbui only when laptop awake Only exposes laptop; meaningless without it
daily_sync, podcast-sync Laptop, launchd scheduled, laptop-bound Hard data dep, scheduled

Workflow

Editing docs:

  1. cd ~/casey-system-docs
  2. Edit markdown
  3. (optional) Local preview: uv run mkdocs serve
  4. git commit && git push
  5. GHA workflow .github/workflows/deploy-docs.yml builds + deploys to Pages. Live in ~30s.

The push-to-main flow is the "deploy" — no separate ritual. If GHA is slow or fails, manual fallback: npx wrangler pages deploy _site.

Removed (cleanup of the tunnel-subpath approach)

  • ~/.cloudflared/config.yml — reverted to single ingress rule (warehouse.caseymanos.com → localhost:8765). No /docs/* path routing anymore.
  • ~/Library/LaunchAgents/com.casey.{uvicorn,mkdocs,cloudflared}.plist — deleted. Were trying to fake always-on for laptop-bound things.
  • ~/garmin-warehouse/scripts/cloudflared_watcher.sh — deleted. Was only needed because of the path-routing complexity.
  • ~/garmin-warehouse/scripts/run_ui.sh — reverted to its original 2-process form (uvicorn + cloudflared). No longer starts mkdocs.

Consequences

Good: - Docs really are 24/7 — Cloudflare Pages has its own SLA, no laptop - One push deploys; no manual sync needed - Removes 3 launchd plists + a wrapper script + tunnel config complexity - ADR 002 + ADR 006 together state a clean rule: "static and data-independent = edge tier; data-bound = laptop tier" - GHA history serves as a deploy log (vs wrangler pages deploy which leaves no audit trail)

Tradeoffs: - Local mkdocs serve preview doesn't match exactly what Pages serves (minor differences in URL paths, etc.) — small in practice - One more GitHub repo (private) to maintain. Trivial. - If Cloudflare Pages quota changes (currently free for our usage), could need to migrate. Migration would be straightforward since the source is just markdown. - Docs no longer behind Cloudflare Access. Repo is private; site contains internal infrastructure details. To re-add gating: CF dashboard → Pages → casey-system-docs → Custom domains → Access policy. Defer until/unless the site gains content that warrants it.

Alternatives considered

  1. GitHub Pages instead of Cloudflare Pages — works fine but would require a separate workflow and we'd lose the option to easily put it behind CF Access if needed.
  2. MkDocs build artifact committed to gh-pages branch — works but feels archaic; CF Pages handles the build better.
  3. Leave docs on the laptop tunnel + add launchd — the path I went down briefly. Wrong tier for the content.

Required follow-up (manual, one-time)

To activate auto-deploy on push:

  1. Create Cloudflare API token with Pages:Edit scope at: https://dash.cloudflare.com/profile/api-tokens
  2. Add to GitHub repo secrets as CLOUDFLARE_API_TOKEN: gh secret set CLOUDFLARE_API_TOKEN --repo caseymanos/casey-system-docs
  3. Optional: Add custom domain docs.caseymanos.com via dashboard (Workers & Pages → casey-system-docs → Custom domains).
  4. Optional: Add Access policy to gate the public site.

References