Architecture
What the bao GitHub Action deploys to your Cloudflare account and how it fits together.
When you run the bao GitHub Action, it provisions and deploys everything directly into your Cloudflare account. Nothing runs on our infrastructure — your auth service, your database, your secrets.
What gets deployed
The action creates two Cloudflare Workers and two storage resources in your account:
Your Cloudflare Account
├─ Workers
│ ├─ {appName}-{env} ← auth service (handles all /api/auth/* traffic)
│ └─ {appName}-rotation-{env} ← secret rotation (runs on a cron schedule)
├─ D1 Database
│ └─ bao_db_{env} ← your users, sessions, and accounts
└─ KV Namespace
└─ bao-config-{env} ← encrypted secrets + your bao.config.jsonAll resource names are scoped by environment (dev, staging, production), so each environment is fully isolated.
Auth service worker
This is your auth layer. It exposes a single base path (/api/auth/*) that handles sign-up, sign-in, sessions, OAuth callbacks, OTP, and any other plugins you have enabled.
On each cold start it reads your configuration and encrypted secrets from KV, then serves requests entirely at the edge — no round-trip to an origin server.
Your app connects to it like any HTTP API:
Your App → POST /api/auth/sign-in → bao auth worker → D1 databaseRotation worker
A separate worker that runs on a cron trigger (quarterly by default). It generates a new auth secret, prepends it to the existing list in KV, and trims old versions beyond your retention limit.
Rotation is non-destructive: active sessions signed with an older secret version remain valid until they expire naturally. No downtime, no forced logouts.
Storage
D1 — your auth database
A Cloudflare D1 (SQLite) database that holds users, sessions, accounts, and any org data if you have the organization plugin enabled. The action runs migrations automatically on every deploy, so the schema is always up to date.
All data lives in your account. bao never has access to it.
KV — config and secrets
A KV namespace (bao-config-{env}) with three keys:
| Key | Contents |
|---|---|
APP_CONFIG | Your bao.config.json, written at deploy time |
BETTER_AUTH_SECRETS | AES-GCM encrypted, versioned auth secrets |
BAO_SECRET_RETENTION_COUNT | How many secret versions to keep (default: 2) |
Secrets are encrypted with a key that only lives in your worker’s environment — never in plaintext, never on our servers.
Environments
The action supports multiple environments. Each one gets its own isolated set of workers and storage resources:
| Resource | Name |
|---|---|
| Auth worker | {appName}-{env} |
| Rotation worker | {appName}-rotation-{env} |
| D1 database | bao_db_{env} |
| KV namespace | bao-config-{env} |
You can deploy to dev, staging, and production independently. Destroying one environment has no effect on the others.
What the action does on each run
Provision infrastructure
Runs OpenTofu to create the D1 database and KV namespace if they don’t exist yet. On subsequent runs this step is a no-op.
Validate and apply your config
Reads bao.config.json, validates it, and writes it into KV so the auth worker picks it up on next cold start — no worker rebuild required for config changes.
Run database migrations
Applies any pending Drizzle migrations to your D1 database via Wrangler.
Deploy the workers
Uploads the auth service worker and rotation worker to your Cloudflare account.
Bootstrap secrets (first run only)
On the very first deploy, generates an initial auth secret, encrypts it, and writes it to KV. On subsequent runs, existing secrets are left untouched unless you pass force_rotate: true.