bao

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.json

All 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 database

Rotation 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:

KeyContents
APP_CONFIGYour bao.config.json, written at deploy time
BETTER_AUTH_SECRETSAES-GCM encrypted, versioned auth secrets
BAO_SECRET_RETENTION_COUNTHow 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:

ResourceName
Auth worker{appName}-{env}
Rotation worker{appName}-rotation-{env}
D1 databasebao_db_{env}
KV namespacebao-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.