Appearance
Hosting & Data Plan
Document type: Engineering Plan — Hosting & Data Status: Approved Last updated: 2026-05-24
This page captures the decisions the platform needs to start building: where code runs, where data lives, what messaging/queue layer we use, and what language the backend is written in. It supersedes section 8 of the Kick-off Plan.
TL;DR
- Cloudflare's free tier alone is not enough to run the platform end-to-end. It is enough for the marketing site, the wiki, and the public edge (DNS, TLS, DDoS, R2 media).
- The platform runs on one Hetzner CX22 box (€3.79/mo, ~$4.50) at MVP, hosting Postgres + Redis + NATS + a Hono backend + a small Python FastAPI sidecar for AI/OCR. Cloudflare sits in front as the edge.
- Backend language: TypeScript on Hono (primary), Python (FastAPI) for the AI/ML sidecar only.
- Total cost at MVP / first-pilot: ~$5/mo + per-txn SMS and mobile-money fees. Production target: ~$30–80/mo.
Why not "everything on Cloudflare free"
Three load-bearing reasons:
- Free Workers is 100,000 requests/day total. Telemetry heartbeats alone exhaust this in minutes — see Operator Journey §5.1. At 1 heartbeat / 30 s × 100 active operators × 16 h/day = ~192,000 req/day. Pilot scale is already over the cap.
- Cloudflare Queues require the Workers Paid plan ($5/mo minimum). There is no free Queues tier. The platform is explicitly event-driven — see Governing Assumptions: "Event-driven governance — each meaningful action must generate an event". A real queue is non-negotiable.
- Cloudflare has no Redis, no MQTT, no Kafka. The closest free analogues are KV (eventually consistent, 1K writes/day on free) and Durable Objects (paid plan only). The free tier cannot host the messaging/queue layer the platform's state machine needs.
Adjacent constraints that matter even on paid Cloudflare:
| Service | Free tier | Why it bites us |
|---|---|---|
| Workers (free) | 100K req/day | Telemetry exhausts this |
| Workers Logs (free) | 200K events/day, 3-day retention | Not enough for an event-sourced audit log |
| D1 (free) | 5 GB, 5M reads/day, 100K writes/day | Audit + telemetry + events exceed it fast |
| R2 (free) | 10 GB, 1M Class-A, 10M Class-B/mo, zero egress | At ~3 MB/scan video, free tier = ~3,300 scans total — one pilot week |
| Workers AI (free) | 10K neurons/day shared | A single Llama-3.2-vision OCR burns 2,500–5,000 neurons → 2–4 OCRs/day |
| Stream | No free tier | Not used — R2 alone is the right home for raw video |
| Zero Trust / Access | Free up to 50 users | Fine for internal tools, not for thousands of operators |
What does stay free on Cloudflare at meaningful scale:
- DNS / CDN / TLS / DDoS — unlimited, free
- Workers + Pages for static-shape sites (marketing, wiki, admin UI)
- R2 for media (zero egress is the real win vs S3 at $0.09/GB)
- Email Routing (inbound team email, free)
- Turnstile (CAPTCHA, free)
- Web Analytics (cookieless, free)
Recommended architecture
Three layers, three hosts.
1. Edge / public surface — Cloudflare (free everything)
| Surface | Stack | Status |
|---|---|---|
kayasync.com, test.kayasync.com | Astro Worker + static assets | ✅ Live |
wiki.kayasync.com | VitePress + password-gate Worker | ✅ Live |
cdn.kayasync.com | R2 bucket, public read for scan media (egress-free) | ⬜ Pending |
app.kayasync.com, admin.kayasync.com | Next.js/Astro Worker calling the API | ⬜ Pending |
Edge services we keep free: DNS, TLS, DDoS, Turnstile, Web Analytics, Email Routing.
2. Backend application + data — one Hetzner CX22 box (€3.79/mo)
Specs: 2 vCPU, 4 GB RAM, 40 GB NVMe, EU data centre, ISO 27001.
Stack (single repo, Docker Compose):
| Component | Role | Notes |
|---|---|---|
| Hono (TypeScript) | Backend HTTP + WS API | Start as a monolith; split per port allocation only when something hurts |
| Postgres | Event store + transactional state + audit log | Single source of truth |
| Redis | Rate limits, sessions, dedupe, telemetry buffer | Self-hosted |
| NATS | Event bus / pub-sub (Kafka/RabbitMQ-equivalent at MVP scale) | Single binary, <50 MB RAM, JetStream for persistence |
| Python FastAPI | OCR + heavy AI sidecar | PaddleOCR for phone-number extraction, OpenCV for video frames |
| Caddy | Reverse proxy + auto-TLS | Single binary, no manual cert renewal |
Capacity headroom: Postgres + Redis + NATS + 6 small services on 4 GB is comfortable up to ~1,000 active operators.
Backups: nightly pg_dump → R2 bucket (free), 30-day retention.
Failover: add a second Hetzner box in a different DC (€3.79) as warm-standby once revenue justifies it.
3. Mobile app — operator client
- React Native + TensorFlow Lite for on-device fingerprint / replay detection
- All capture flows work offline per Feature Inventory §M9; sync via background queue
- FCM for push (unlimited free), Whisper via Workers AI for voice confirmation (well under 10K neurons/day for short clips)
Capability → service mapping
Comprehensive map for every cross-cutting need in Feature Inventory. "Free?" assumes MVP / first-pilot scale.
| Capability | Recommended service | Free at MVP? |
|---|---|---|
| Marketing site (live) | Cloudflare Workers + static assets | ✅ |
| Internal wiki (live) | Cloudflare Pages + Worker password gate | ✅ |
| 12 backend microservices | Hetzner CX22, Docker Compose | €3.79/mo |
| Postgres (event store, txn state, multi-tenant) | Self-host on the same box (or Neon free hosted) | ✅ |
| Redis (rate limit, session, dedupe, telemetry buffer) | Self-host on the same box (or Upstash free 500K cmd/mo) | ✅ |
| Message queue / event bus | NATS self-hosted (Postgres LISTEN/NOTIFY for the very first cut) | ✅ |
| Telemetry heartbeat ingestion | Hono /telemetry endpoint, batched, into NATS | ✅ |
| Object storage (video evidence, SOD photos, agreement videos) | Cloudflare R2 | ✅ <10 GB, then $0.015/GB-mo |
| Video AI integrity (fingerprint, replay, tamper) | On-device TensorFlow Lite + server-side Python sidecar | ✅ |
| OCR (designee phone number) | PaddleOCR on the Python sidecar; fall back to Workers AI vision for low-confidence | ✅ |
| Speech-to-text (voice confirmations) | Cloudflare Workers AI Whisper | ✅ |
| Text-to-speech (low-literacy prompts) | Pre-record Prompt Vocabulary as MP3s in R2 | ✅ |
| Authentication (operator phone+OTP, partner SSO) | Hono + JWT + bcrypt against Postgres; SMS OTP via Africa's Talking | ✅ code; SMS paid per msg |
| Push notifications | Firebase Cloud Messaging (unlimited free) | ✅ |
| Transactional email | Resend (already wired, 3,000/mo free) | ✅ |
| Mobile money payouts | Flutterwave (per-txn fees accepted) | n/a |
| Admin / supervisor dashboard | Single Next.js/Astro app behind Cloudflare Access (free ≤ 50 users) | ✅ |
| Enterprise client portal (M12) | Same app, different RBAC scope | ✅ |
| Regulator / partner exports (M10, M13) | Postgres view + on-demand CSV/JSON dump | ✅ |
| Observability (logs, metrics, traces) | Grafana Cloud free (10K series, 50 GB logs, 14d) OR self-hosted Grafana + Loki + Prometheus | ✅ |
| Error tracking | Sentry free (5K errors/mo) or self-hosted GlitchTip | ✅ |
| Background / scheduled jobs | Hono cron loop polling NATS / Postgres | ✅ |
Backend language
Primary: TypeScript on Hono
- Runs identically on Cloudflare Workers, Node, Bun, and Deno — strategic optionality for free. Move a handler from Workers to the Hetzner box (or back) without rewriting.
- Light, modern, Zod-friendly, OpenAPI-ready
- The team is already deep in TS (Astro website, auth-worker) — no language switch needed
- Shared types between mobile (React Native + TS) and backend cut a whole class of bugs
Secondary: Python (FastAPI)
Only for the AI/ML sidecar — OCR (PaddleOCR), video frame analysis (OpenCV), fingerprint extraction. Python's CV/ML ecosystem is not negotiable here. Keep the sidecar small and behind an internal HTTP endpoint.
Future option: Go
If the telemetry ingestor becomes a bottleneck, Go's goroutines + GC profile fit high-fanout streaming better than Node. Don't pre-optimise — only switch when traces show it's needed.
Not recommended
- Rust — wrong cost/benefit for a small team's MVP; revisit if a specific service becomes perf-critical
- Elixir / Phoenix — technically perfect for the real-time dispatcher fanout, but hiring + ecosystem are too thin for a Ghana-based team at MVP. Re-evaluate at 10+ engineers.
- Python as primary — would slow iteration vs TS and force two languages on the frontend anyway
Phasing
Cost stays near zero while pilot is small.
| Phase | When | Cost / mo | What's running |
|---|---|---|---|
| 0 — now | Today | $0 | Marketing site + wiki on CF free; lead form via Resend free |
| 1 — first end-to-end demo | Before first pilot capture | ~$5 | Add Hetzner CX22; Postgres + Redis + NATS + Hono API + Python sidecar; R2 bucket for media; FCM for push; Africa's Talking for SMS (paid per msg) |
| 2 — controlled pilot live | Pilot day 1 | ~$10 | Add a second Hetzner box as warm standby; Grafana Cloud free for metrics; Sentry free for errors; nightly Postgres backups to R2 |
| 3 — production for paying enterprise | Post-pilot | ~$30–80 | Upgrade Postgres to a CCX13 (€16) for headroom; add CDN-fronted media transcoding if needed; CF Workers Paid ($5) if any edge function exceeds free quota; Sentry team ($26) if error volume justifies |
| 4 — multi-region | When a second corridor / country joins | varies | Replicate Hetzner stack to a US/SA region; add CF Hyperdrive if Workers ever need to read the Postgres |
Going "pure Cloudflare Paid" instead would land at $30–80/mo at pilot scale, with sharper bills as you grow, and still no real Redis / queue / Postgres. Strictly worse than the Hetzner+CF-edge hybrid.
Open questions / decisions still to make
- Jurisdiction. Hetzner is EU-only by default. If Ghana data-residency rules require West-African hosting, swap for DigitalOcean ($4/mo basic droplet, same specs — already in Kick-off Plan §8) or AWS af-south-1 (~5× the cost). Legal to confirm.
- Pilot scale. Concrete numbers from NKA: operators in pilot 1, scans/operator/day, corridors. Re-check D1 / R2 / Postgres write projections once known.
- Hono spike. Before committing the monorepo shape, build one endpoint (
/v1/scans) that runs both on Workers and on the Hetzner box, reading/writing the same Postgres via Hyperdrive. If it feels right, commit. If TS feels awkward for the telemetry ingestor specifically, swap that one service to Go.
Repo shape (for execution)
When Phase 1 starts, the new monorepo kayasync-platform follows Turborepo per Kick-off Plan §2:
kayasync-platform/
├── apps/
│ ├── api/ # Hono backend
│ ├── admin/ # Internal dispatch/supervisor UI
│ ├── client/ # Enterprise client portal
│ └── mobile/ # React Native operator app
├── services/
│ └── ai-sidecar/ # Python FastAPI (OCR + video AI)
├── packages/
│ └── shared-types/ # Zod schemas shared with mobile + admin
└── infra/
├── docker-compose.yml
├── Caddyfile
└── backup/Sources
Cloudflare: Workers pricing · Workers limits · R2 pricing · D1 pricing · KV pricing · Workers AI pricing · Vectorize pricing · Stream pricing · Zero Trust · Email Routing limits
Alternatives: Hetzner Cloud · Neon Postgres · Supabase · Upstash · Fly.io · Render · Firebase / FCM · Flutterwave Ghana · Africa's Talking Ghana