Local dev tunneling — Wrangler + Astro dev + Cloudflare Tunnel
Local dev tunneling — Wrangler + Astro dev + Cloudflare Tunnel
Decision
Local development across the family runs on three substrates, picked by surface:
- Wrangler dev — for
every Cloudflare Worker (the umbrella
api.oriz.inHono Worker perhono-worker-api-umbrella, thes.oriz.inshortener Worker, theog.oriz.inSatori 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. - Astro dev — for every site (Astro / Vite stack) per
cloudflare-pages-for-all-sites.pnpm devboots the Vite dev server with HMR. - 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
- Cloudflare Tunnel is free, native to the family's stack, and
has no auth limit. The family already runs on Cloudflare
(Pages + Workers + DNS + Email Routing). One more
cloudflaredbinary fits without a new vendor surface — no card on file (rules/no-card-on-file), no subscription (rules/no-subscriptions), no anonymous-user TTL. - Persistent hostnames. A
cloudflared tunnel route dnsbinding todev.oriz.insurvives laptop reboots and dynamic IPs — ngrok's free tier rotates the hostname every session, forcing webhook re-registration every dev day. - Wrangler handles Worker runtime parity. Local-mode workerd matches Cloudflare's edge runtime; remote-mode hits real bindings. Nothing else does this for the Worker stack.
- Astro dev handles HMR + MDX + Vite plugins. Native Vite HMR on the same code path as the production build avoids staging surprises.
- Three tools, one surface each, no overlap. Wrangler doesn't tunnel; cloudflared doesn't run Workers; Astro dev doesn't expose ports publicly. Composed cleanly.
Why not the rejected options
| Tool | Why rejected |
|---|---|
| ngrok | Free tier rotates hostname every session, forcing webhook re-registration; persistent hostnames require paid plan + card. Anonymous use throttled |
| localtunnel | Hostname is random subdomain on loca.lt, no persistent binding to *.oriz.in; OSS but unmaintained |
| serveo | SSH-tunnel-shaped — no *.oriz.in binding; reliability issues over time |
| bore / frp / pagekite | Self-host or paid past tiny envelope; the family runs only managed serverless |
| Tailscale Funnel | Requires Tailscale-installed receiving party — fits internal collaboration, not public-internet webhooks |
| GitHub Codespaces port-forward | Fits 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
- Razorpay — point the dashboard webhook at
https://dev.oriz.in/webhooks/razorpaywhile testing payment flows; production points atapi.oriz.in/webhooks/razorpayfronted by Hookdeck. - GitHub (PR-checks / push events for
oriz-omnipost) — same pattern; dev webhook points atdev.oriz.in/gh. - Bluesky AT Protocol firehose — for the
lifestream-federationconsumer, run cloudflared during dev to receive jetstream events on a public hostname. - EmailOctopus / Buttondown webhooks for newsletter signup acknowledgements.
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
- No ngrok account. No
NGROK_AUTH_TOKENin. - No paid Wrangler / Cloudflare plan for local dev — Wrangler is free; Cloudflare Tunnel is free; Workers free tier covers remote-mode parity testing.
- No tunneling for production. Production traffic hits Cloudflare's edge directly via Pages / Workers — tunnels are exclusively a local-dev surface.
Failure modes documented
cloudflareddaemon crashes ? restart; tunnel UUID and DNS binding are durable on the CF side.- Account quota: Cloudflare Tunnel has no per-user quota on the free plan — bandwidth and connection counts are unlimited for personal / dev use.
Cross-refs
- Cloudflare Tunnel service entry
- Wrangler service entry
- Dev-tools index
- Cloudflare Pages for all sites
- Hono Worker API umbrella — primary Worker tested under Wrangler dev
- Hookdeck for webhook reliability — production webhook ingress
- Secrets management — Doppler
- No card-on-file rule
- No subscriptions rule