type: decision
status: active
timestamp: 2026-06-20
tags: [decisions, architecture, dev-tools, tunneling, cloudflare-tunnel, wrangler, astro, webhook-testing]

Local dev tunneling — Wrangler + Astro dev + Cloudflare Tunnel

Local dev runs on three substrates via CF Tunnel \ picked by surface \u2014 Wrangler dev for Cloudflare Workers, Astro dev for sites,\ \ Cloudflare Tunnel (cloudflared) for exposing localhost to the public internet\ \ for webhook testing. ngrok and localtunnel REJECTED."

Local dev tunneling — Wrangler + Astro dev + Cloudflare Tunnel

Decision

Local development across the family runs on three substrates, picked by surface:

  1. Wrangler dev — for every Cloudflare Worker (the umbrella api.oriz.in Hono Worker per hono-worker-api-umbrella, the s.oriz.in shortener Worker, the og.oriz.in Satori endpoint). Local mode (wrangler dev) runs in workerd; remote mode (wrangler dev --remote) runs against real Cloudflare infrastructure for KV / R2 / Queues / Durable Objects parity.
  2. Astro dev — for every site (Astro / Vite stack) per cloudflare-pages-for-all-sites. pnpm dev boots the Vite dev server with HMR.
  3. Cloudflare Tunnel (cloudflared) — for exposing localhost:<port> to a public hostname so that webhook senders (Razorpay, GitHub, Bluesky AT Protocol firehose, EmailOctopus) can reach the in-flight Worker / site during development.

ngrok, localtunnel, serveo, and bore — all REJECTED.

User direction 2026-06-20: “Wrangler + Astro dev locked. ALSO add Cloudflare Tunnel (free, CF-native).”

Why

Why not the rejected options

ToolWhy rejected
ngrokFree tier rotates hostname every session, forcing webhook re-registration; persistent hostnames require paid plan + card. Anonymous use throttled
localtunnelHostname is random subdomain on loca.lt, no persistent binding to *.oriz.in; OSS but unmaintained
serveoSSH-tunnel-shaped — no *.oriz.in binding; reliability issues over time
bore / frp / pagekiteSelf-host or paid past tiny envelope; the family runs only managed serverless
Tailscale FunnelRequires Tailscale-installed receiving party — fits internal collaboration, not public-internet webhooks
GitHub Codespaces port-forwardFits a Codespaces workflow; the family develops locally, not in Codespaces

Implications

Setup (one-time per developer machine)

# Install cloudflared (Windows: winget install cloudflare.cloudflared)
cloudflared tunnel login                       # browser-auth into the CF account
cloudflared tunnel create dev-oriz             # mints a tunnel UUID
cloudflared tunnel route dns dev-oriz dev.oriz.in

Result: dev.oriz.in resolves to the tunnel UUID; whatever cloudflared tunnel run dev-oriz is pointing at receives traffic.

Per-session local dev flow

# Terminal 1 — Worker
cd packages/oriz-api-worker && wrangler dev --port 8787

# Terminal 2 — Site (one of the 11)
cd sites/oriz-blog-site && pnpm dev   # Astro on :3000

# Terminal 3 — public hostname pointing at one of the above
cloudflared tunnel run --url http://localhost:8787 dev-oriz
# now https://dev.oriz.in tunnels to localhost:8787

cloudflared config file at ~/.cloudflared/config.yml:

tunnel: dev-oriz
credentials-file: ~/.cloudflared/<UUID>.json
ingress:
  - hostname: dev.oriz.in
    service: http://localhost:8787
  - service: http_status:404

Webhook testing surfaces

Secrets parity

wrangler dev reads .dev.vars for local secrets; the same keys also exist in Doppler per secrets-management-doppler. doppler run -- wrangler dev keeps the local secrets in sync with Doppler without committing them.

What we don’t do

Failure modes documented

Cross-refs


Edit on GitHub · Back to index