Deploy to Cloudflare Workers with D1 or Postgres + Hyperdrive
Run a ShipAny (TanStack Start) SaaS on Cloudflare Workers — D1 for zero infrastructure, or your existing Postgres through Hyperdrive. Setup, verification and the pitfalls we hit migrating shipany.ai.
Last updated: Oct 8, 2026
shipany.ai itself runs on Cloudflare Workers: a ShipAny TanStack app talking to an existing Postgres database through Hyperdrive. This page covers both supported setups, how to deploy, and the pitfalls we hit during that migration.
Two database options
| D1 | Postgres + Hyperdrive | |
|---|---|---|
| Best for | New projects, zero external infrastructure | You already have Postgres (RDS, Neon, Supabase, self-hosted) or need Postgres features |
| Binding | d1_databases → DB | hyperdrive → HYPERDRIVE |
| Schema | wrangler d1 migrations apply --remote | pnpm db:migrate straight against Postgres (not through Hyperdrive) |
| Bundle | Postgres driver stubbed out | Postgres driver kept |
The backend is selected by vars.DATABASE_PROVIDER in wrangler.jsonc (d1 or postgresql); the build reads it to decide which driver goes into the Worker bundle.
Deploying
The template includes a deploy-cloudflare agent skill that automates all of this. By hand, the steps are:
- Create the database binding —
npx wrangler d1 create <name>, or for Postgresnpx wrangler hyperdrive create <name> --connection-string="postgres://…". - Fill in
wrangler.jsonc— worker name, an explicitcompatibility_date,nodejs_compat,vars(DATABASE_PROVIDER,VITE_APP_URL,VITE_APP_NAME) and the binding from step 1. - Set secrets —
npx wrangler secret put AUTH_SECRET(andCONFIG_ENCRYPTION_KEYif you encrypt settings). - Put public build-time values in
.env.production—VITE_APP_URLis baked into the bundle at build time. - Deploy —
pnpm run cf:deployloads.env.production, builds with thecloudflare_modulepreset and runswrangler deploy.
Moving an existing domain with zero downtime
If your site already runs elsewhere behind Cloudflare DNS, you don't need to touch DNS at all. Add a Worker Route for example.com/*; the Worker intercepts traffic before it reaches the old origin. To roll back, delete the route — traffic goes back to the old origin within seconds. Keep the old deployment running until you've verified payments and sign-in.
Pitfalls we hit
Can't set compatibility date in the future— the build fills in your machine's local date. In timezones ahead of UTC this can be "tomorrow" for Cloudflare. Setcompatibility_dateexplicitly.- Admin settings silently missing with Hyperdrive — with Hyperdrive there's no
DATABASE_URL, and an older settings loader treated that as "no database" and returned empty settings. Pages still rendered, but payment and OAuth providers were missing ("No payment provider configured"). Make sure your settings loader recognizes theHYPERDRIVEbinding. - Turn off Hyperdrive's query cache for transactional apps — by default it caches reads for up to 60 seconds, so order status, credit balances or settings can look stale. The connection pool, not the cache, is what makes Hyperdrive fast:
npx wrangler hyperdrive update <id> --caching-disabled=true. - Reusing a production database? Don't run migrations. If the tables were created by another system, Drizzle's migration journal is empty and
db:migratewould try to create everything from scratch. - Keep
AUTH_SECRETidentical to your previous deployment, or every user gets signed out.
Verify after every deploy
A green status code isn't enough. Check that:
- The homepage's
/assets/*files match your new build (so traffic really reaches the Worker). /api/config/publicreturns your configured switches (stripe_enabled,google_auth_enabled, …), identical to the previous deployment.- Database connections arrive from Cloudflare IP ranges (Hyperdrive), and
npx wrangler tailshows no exceptions. - Sign-in and one real payment per provider work end to end.
