1---
2title: "Part 6: Deploy and verify"
3description: Deploy the authenticated API, configure production OAuth callbacks, and verify session boundaries.
4sidebar:
5 order: 6
6---
7
8Continue from [Part 5](/better-auth/tutorial/part-5). Use a separate deployment stage and GitHub OAuth application for production.
9
10## Choose a production domain
11
12If your domain is managed by Cloudflare, attach it to the Worker:
13
14```diff lang="typescript"
15 // src/worker.ts
16 {
17 main: import.meta.url,
18+ domain: "auth.example.com",
19 assets: "./public",
20```
21
22Replace the example with a hostname you own. Alchemy configures the Worker domain; see [custom domains](/cloudflare/networking/custom-domains) for DNS prerequisites and workers.dev alternatives.
23
24## Register the production OAuth app
25
26Create another [GitHub OAuth application](https://github.com/settings/applications/new):
27
28```text
29Application name: Auth tutorial — production
30Homepage URL: https://auth.example.com
31Authorization callback URL: https://auth.example.com/api/auth/callback/github
32```
33
34Leave the local application's callback unchanged.
35
36## Configure the production origin
37
38```diff lang="typescript"
39 // src/auth.ts
40+import * as Option from "effect/Option";
41+
42 const makeAuth = Effect.gen(function* () {
43+ const baseURL = yield* Config.String("AUTH_BASE_URL").pipe(Config.option);
44 const clientId = yield* Config.String("GITHUB_CLIENT_ID");
45 const clientSecret = yield* Config.Redacted("GITHUB_CLIENT_SECRET");
46 return yield* BetterAuth({
47 basePath: "/api/auth",
48 emailAndPassword: { enabled: true },
49+ baseURL: Option.getOrUndefined(baseURL),
50 socialProviders: {
51```
52
53An explicit origin makes production callback construction predictable. This is application URL configuration, not a replacement for Alchemy's generated signing secret.
54
55## Keep the local origin automatic
56
57Leave `AUTH_BASE_URL` unset locally; Better Auth derives it from the incoming request. Keep the local GitHub application's callback aligned with the URL Alchemy prints, updating it if a restart changes the address.
58
59## Supply production credentials
60
61```dotenv
62# .env.production
63AUTH_BASE_URL=https://auth.example.com
64GITHUB_CLIENT_ID=your-production-oauth-client-id
65GITHUB_CLIENT_SECRET=your-production-oauth-client-secret
66```
67
68Keep this file out of Git. In CI, supply the same configuration names through the deployment environment's secret store.
69
70## Add build-and-deploy commands
71
72Add these scripts to your existing package manifest:
73
74```json
75{
76 "scripts": {
77 "dev": "bun build ./src/ui.ts --target browser --outdir ./public && alchemy dev",
78 "deploy": "bun build ./src/ui.ts --target browser --outdir ./public && alchemy deploy"
79 }
80}
81```
82
83Both commands rebuild the browser bundle before starting Alchemy.
84
85## Deploy a separate stage
86
87```sh
88bun --env-file=.env.production run deploy --stage production
89```
90
91Alchemy provisions a separate database and signing secret for this stage, applies schema changes, and deploys the Worker. Subsequent deploys retain the same secret and database identities through stack state.
92
93## Check anonymous access
94
95```sh
96curl -i https://auth.example.com/api/health
97curl -i https://auth.example.com/api/me
98```
99
100The health check should return `200`; the protected endpoint should return `401`.
101
102## Check browser authentication
103
104Open your production site, create an account, and select **Call protected API**. Sign out and verify the same button asks you to sign in.
105
106Sign in with GitHub and repeat the protected request. Cancel a second GitHub sign-in attempt to verify the error message appears after the callback.
107
108## Check persistence
109
110```sh
111bun --env-file=.env.production run deploy --stage production
112```
113
114Refresh a previously signed-in browser after redeployment. Its existing session should still work; do not delete stack state or rotate the signing secret to deploy an update.
115
116## Keep future changes safe
117
118Review [migrations](/better-auth/guides/migrations) before changing the schema, and preserve the [signing identity](/better-auth/guides/configuration#preserve-the-signing-identity) across deployments. For authentication features beyond the Alchemy integration, see the [Better Auth documentation](https://better-auth.com/docs).
119
120The [complete companion application](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-better-auth) contains the assembled files. Its GitHub integration is opt-in so the email/password flow runs before external credentials are available.