1---
2title: Config and secrets
3description: Bind Effect Config into an Alchemy host, compose database layers, and preserve generated signing secrets.
4---
5
6## Keep configuration in a typed service
7
8```typescript
9// auth.ts
10import { BetterAuth } from "@alchemy.run/better-auth";
11import * as Config from "effect/Config";
12import * as Context from "effect/Context";
13import * as Effect from "effect/Effect";
14import * as Layer from "effect/Layer";
15import * as Option from "effect/Option";
16
17const makeAuth = Effect.gen(function* () {
18 const baseURL = yield* Config.String("AUTH_BASE_URL").pipe(Config.option);
19 return yield* BetterAuth({
20 basePath: "/api/auth",
21 emailAndPassword: { enabled: true },
22 baseURL: Option.getOrUndefined(baseURL),
23 });
24});
25
26export class Auth extends Context.Service<
27 Auth,
28 Effect.Success<typeof makeAuth>
29>()("app/Auth") {
30 static readonly layer = Layer.effect(Auth, makeAuth);
31}
32```
33
34`BetterAuth` checks the inline options and infers plugin types. The wrapper owns `database` and `secondaryStorage`; supply those through layers rather than options.
35
36`AUTH_BASE_URL` is optional and contains an origin without a trailing slash when supplied. Resolving Config during service construction lets Alchemy discover and bind it before requests arrive.
37
38## Supply the database at the host
39
40```typescript
41// worker.ts
42import { CloudflareD1 } from "@alchemy.run/better-auth/CloudflareD1";
43import * as Cloudflare from "alchemy/Cloudflare";
44import * as Effect from "effect/Effect";
45import * as Layer from "effect/Layer";
46import { HttpServerRequest } from "effect/http/HttpServerRequest";
47import * as HttpServerResponse from "effect/http/HttpServerResponse";
48import { Auth } from "./auth.ts";
49
50const AuthDb = Cloudflare.D1.Database("AuthDb");
51
52export default class AuthApi extends Cloudflare.Worker<AuthApi>()(
53 "AuthApi",
54 { main: import.meta.url, compatibility: { flags: ["nodejs_compat"] } },
55 Effect.gen(function* () {
56 const auth = yield* Auth;
57 return {
58 fetch: Effect.gen(function* () {
59 const request = yield* HttpServerRequest;
60 const path = request.url.split("?")[0];
61 if (path === "/api/auth" || path.startsWith("/api/auth/")) {
62 return yield* auth.fetch;
63 }
64 return HttpServerResponse.text("Not found", { status: 404 });
65 }),
66 };
67 }).pipe(
68 Effect.provide(Auth.layer.pipe(Layer.provide(CloudflareD1(AuthDb)))),
69 ),
70) {}
71```
72
73This host creates its own D1 database, registers migrations, and forwards Better Auth's complete HTTP responses. The Auth service itself has no Cloudflare dependency.
74
75## Run the standalone stack
76
77```typescript
78// alchemy.run.ts
79import * as Alchemy from "alchemy";
80import * as Cloudflare from "alchemy/Cloudflare";
81import * as Effect from "effect/Effect";
82import AuthApi from "./worker.ts";
83
84export default Alchemy.Stack(
85 "AuthConfiguration",
86 { providers: Cloudflare.providers(), state: Alchemy.localState() },
87 Effect.gen(function* () {
88 const api = yield* AuthApi;
89 return { url: api.url };
90 }),
91);
92```
93
94These three files use Bun, matching releases of `alchemy` and `@alchemy.run/better-auth`, Better Auth 1.7.5, Effect 4, and `kysely`. Configure Cloudflare credentials for deployment; preserve the local Alchemy state directory across runs.
95
96```sh
97bunx alchemy dev
98```
99
100Alchemy selects an available port and prints the origin. With `AUTH_BASE_URL` unset, Better Auth derives its origin from the incoming request, so no fixed port is needed.
101
102## Override the origin deliberately
103
104```sh
105read -r AUTH_BASE_URL
106export AUTH_BASE_URL
107bunx alchemy deploy --stage prod
108```
109
110Enter the actual origin serving this Worker, without a path or trailing slash. `baseURL` does not provision DNS or attach a custom domain; configure that routing separately before sending traffic there.
111
112A gitignored env file can supply the same value through `--env-file .env.production`. Pass the file explicitly on subsequent deployments, and update any local override if Alchemy's printed origin changes.
113
114`--stage prod` selects the stack instance; `--profile prod` selects cloud credentials. Neither option implicitly loads an env file or selects an application URL; keep local development on a separate stage.
115
116## Preserve the signing identity
117
118Omit `secret` for a new application: Alchemy provisions a random signing secret once and preserves it across deployments. Keep the auth instance's `id`, resource namespace, stage, and state stable.
119
120When importing an existing installation, preserve its existing signing identity through protected configuration rather than generating a replacement. Preserving a secret alone does not migrate users, sessions, or cookies; follow the [upgrade guidance](/better-auth/upgrades/from-1-6-to-1-7) before moving an existing database.