ADR-0001 — Cloudflare stack as the runtime
- Status: Accepted
- Date: 2026-04-15
- Decision-makers: Tom Anderson
- Supersedes: —
- Superseded by: —
Context
Helm needs a runtime that can host:
- One isolated app instance per shop
- A SQL database per shop with low operational overhead
- Object storage per shop for receipts/photos
- Scheduled jobs per shop
- A static documentation site (this bible)
- A static public marketing site per shop
Constraints:
- One developer for the foreseeable future. Ops surface must be small.
- Per-shop blast radius — a problem at one shop must not affect another.
- POS UX matters; cold-start latency budget is < 100ms.
- Shops are in Canada (BC start), so a Canadian PoP is preferable.
Alternatives evaluated:
- AWS (Lambda + RDS + S3 + CloudFront + EventBridge) — battle-tested but per-shop infra is heavy: VPCs, security groups, RDS instances, IAM roles. Ops surface vastly larger than a one-person team can sustain at 50 shops. Cold-start is acceptable but worse than Workers.
- Vercel (Functions + Vercel Postgres + Blob + Cron) — Functions cold-start hurts POS UX. Postgres is multi-tenant by default; per-shop Postgres requires per-shop projects, multiplying ops. Not a great fit for the single-tenant model.
- Self-hosted (VPS or k8s + Postgres + MinIO + scheduled jobs) — operationally untenable for one person responsible for shop SLAs.
- Cloudflare (Workers + D1 + R2 + Pages + Cron) — single platform; per-shop deploys are first-class; Workers cold-start is sub-10ms; D1 is single-tenant SQLite that's easy to back up and reason about; R2 is S3-compatible with no egress fees; global PoPs include Toronto and Vancouver.
Decision
Use Cloudflare Workers + D1 + R2 + Pages + Cron as the entire runtime. No mix-and-match with another cloud. All infrastructure is Cloudflare; deploys are via wrangler.
Consequences
Positive:
- One vendor, one CLI, one billing, one set of docs to learn.
- Per-shop isolation is the platform's natural unit (one Worker = one deploy = one shop).
- Sub-10ms cold start makes the POS responsive.
- D1's SQLite-based model means schema, backups, and exports are simple files.
- R2's no-egress-fees pricing is a meaningful saving at scale.
- Cloudflare Access on Pages gives us SSO for the bible without extra infra.
Negative:
- Vendor lock-in. Migration off Cloudflare would be a large project. Mitigation: the schema is plain SQLite (portable), the JS is plain JS (portable), the bindings are the lock-in surface and they're a small number of well-documented APIs.
- D1 is younger than Postgres; some advanced features (full-text search, partial indexes) lag.
- Workers' CPU-time limit (50ms standard, configurable up) means heavy computation needs to be moved out of the request path.
- A Cloudflare-wide outage takes all shops down simultaneously — see single-tenant per shop for what isolation we still get and what we don't.
Notes
This ADR is foundational; most other ADRs build on it. Revisit if Cloudflare changes pricing structure significantly or if D1 adds limits incompatible with our scale.
See also
- Tech stack summary
- C4 — Container
- Deployment topology
- ADR-0002: D1 over Postgres