Deploy to Cloudflare
Take your Roost application from local development to production on Cloudflare Workers.
What you'll learn
- How to deploy a Roost app to Cloudflare Workers
- How to set production secrets in the Cloudflare dashboard
- How to attach a custom domain to your deployed app
Time: ~20 minutes
Prerequisites: A working local Roost app and a Cloudflare account.
Packages used: @roostjs/cloudflare, @roostjs/start, @roostjs/cli
Step 1: Verify your local app works
Before deploying, confirm everything runs cleanly on your machine.
bun run devYou should see the dev server start and print a local URL. Open http://localhost:3000 in your browser and confirm your app loads without errors.
Fix any issues locally before continuing — deployments are much easier to debug when you
start from a known-good state.
Step 2: Review wrangler.jsonc
Open wrangler.jsonc at the root of your project. Roost scaffolds this file for
you, but it's worth understanding what's there.
{
"name": "my-app",
"compatibility_date": "2024-01-01",
// D1 — serverless SQL database
"d1_databases": [
{
"binding": "DB",
"database_name": "my-app-db",
"database_id": "YOUR_D1_DATABASE_ID"
}
],
// KV — key-value store used for sessions and caching
"kv_namespaces": [
{
"binding": "KV",
"id": "YOUR_KV_NAMESPACE_ID"
}
],
// AI — Cloudflare Workers AI for built-in model inference
"ai": {
"binding": "AI"
}
}You should see bindings for DB (D1 database), KV (key-value store), and AI (Workers AI). The database_id and KV id fields need to be filled in with real resource IDs before deploying. Create a D1 database with bunx wrangler d1 create my-app-db and a KV namespace with bunx wrangler kv namespace create KV, then paste the returned IDs into wrangler.jsonc.
Step 3: Sign up for Cloudflare
If you don't have a Cloudflare account yet, create one at dash.cloudflare.com/sign-up. The free tier is enough to deploy and run your app.
You should see the Cloudflare dashboard after signing in. Keep this tab open — you'll use it in the next step.
Step 4: Set production environment variables
Your .dev.vars file holds local secrets but is never deployed. Production secrets live in the Cloudflare dashboard.
In the Cloudflare dashboard, go to Workers & Pages, select your worker (it will appear after the first deploy if it doesn't exist yet — come back to this step then), and open Settings > Variables. Add each secret as an encrypted environment variable:
WORKOS_API_KEY = sk_live_...
WORKOS_CLIENT_ID = client_...
# If you use billing
STRIPE_SECRET_KEY = sk_live_...
STRIPE_WEBHOOK_SECRET = whsec_...Use the Encrypt toggle for each value so it's never shown in plaintext again after saving. Cloudflare will inject these automatically at runtime.
You should see each variable listed under Environment Variables with an encrypted badge.
Step 5: Deploy with roost deploy
Run the deploy command from your project root:
roost deployRoost builds your app with Vite and publishes it to Cloudflare Workers via Wrangler. The output looks like this:
Building application...
✓ Vite bundle complete (142 kB)
Deploying to Cloudflare Workers...
✓ Uploaded my-app (2.3 sec)
✓ Published my-app
https://my-app.your-subdomain.workers.devroost deploy does not run migrations. To apply your schema to the production D1
database, run bunx wrangler d1 execute my-app-db --remote --file schema.sql or
use bunx drizzle-kit push pointed at your production environment before or after deploying.
You should see a workers.dev URL printed at the end of the output. Copy it — you'll use it in the next step.
Step 6: Visit the live URL
Open the workers.dev URL from the deploy output in your browser. Your app is
now running on Cloudflare's global network.
You should see the same app you tested locally in Step 1. Try logging in and exercising a few routes to confirm the production environment is wired up correctly. If anything looks wrong, check the Cloudflare dashboard under Workers > Logs for error details.
Step 7: Set up a custom domain
To serve your app from your own domain instead of workers.dev, open the
Cloudflare dashboard, go to Workers & Pages, select your worker, and
click Settings > Triggers > Add Custom Domain. Enter your domain
(e.g. app.example.com) and click Add Custom Domain.
Cloudflare will create the DNS record automatically if your domain's nameservers point to Cloudflare. If they don't, you'll see instructions to add a CNAME record at your registrar.
You should see your custom domain listed under Triggers with a green Active status after the DNS propagates (usually under a minute when using Cloudflare DNS).
Step 8: Make a change and redeploy
Edit any file in your app — for example, change a heading in one of your routes. Then deploy again:
roost deployYou should see the same build and deploy output as before, completing in a few seconds. Reload your custom domain in the browser and you should see your change live immediately. No restarts, no servers to manage — Cloudflare's edge deploys the new version globally within seconds.
Next steps
- @roostjs/cloudflare — D1, KV, AI bindings in depth
- @roostjs/auth — configure production WorkOS callbacks
- @roostjs/billing — connect Stripe webhooks for production
- @roostjs/queue — background jobs with Cloudflare Queues