brisk

Hosting Brisk

Brisk is one Cloudflare Worker plus R2, D1, and Durable Objects — or the same app self-hosted on any Node box with SQLite and a disk. Stand up your own instance, point it at a domain, and decide who gets in. This is the operator's page — building sites on an instance is in the docs.

How it works

Every request to every site hits a single Cloudflare Worker. It reads the site from the subdomain (or a /s/<name>/ path), then either streams that site's static files straight from R2, or — for brisk.* calls under /api/ — talks to the backing primitives: D1 for the database, R2 for uploads, a Durable Object per site for realtime, and a server-side key for AI. An optional Google OAuth gate sits in front; with it off, every visitor is a trusted dev user.

Brisk architecture: site subdomains pass through optional Google OAuth into one Cloudflare Worker, which serves static files from R2 and routes /api/* to D1, R2, Durable Objects, the AI proxy, identity, and hosting.

Stand it up

From the repo: create the resources, apply the schema, ship the Worker.

cd worker
export CLOUDFLARE_ACCOUNT_ID=...      # or run `wrangler login` (single account)
npx wrangler d1 create brisk          # paste the id into wrangler.jsonc
npx wrangler r2 bucket create brisk
npx wrangler d1 migrations apply brisk --remote
pnpm --filter @usebrisk/sdk build        # bundle the SDK into the worker assets
npx wrangler deploy

That alone gives path-mode URLs (https://brisk.<account>.workers.dev/s/foo/) with no auth — fine on a private network. The full experience adds wildcard subdomains and a login.

Instance config (BASE_HOST, AUTH, the allowlists, VISIBILITY) isn't committed to wrangler.jsonc — it's deployment-specific. Set it as Variables in the dashboard (Workers & Pages → your worker → Settings → Variables and Secrets); every var is optional and defaults to a path-only, no-auth, private instance. Locally, wrangler dev reads them from .dev.vars.

Self-hosting (Docker, Compose, Kubernetes)

Cloudflare is the reference target, not a requirement. The same core runs as a plain Node server with the platform's seams bound to SQLite, the filesystem or any S3-compatible bucket, and in-process websockets — the SDK, the dashboard, and every deployed site behave identically. The fastest way in is the wizard, which asks a few questions and writes the config for whichever target you pick. It lives in this repo, so run it from a checkout:

pnpm install && pnpm build
node create-brisk/dist/index.js

Or go straight to the artifacts in deploy/:

# a single VM
cp deploy/.env.example deploy/.env      # set AUTH, BASE_HOST, secrets…
docker compose -f deploy/docker-compose.yml up -d

# Kubernetes
helm install brisk deploy/helm/brisk \
  --set config.baseHost=brisk.example.com \
  --set config.auth=google \
  --set secrets.googleClientId=… \
  --set secrets.googleClientSecret=… \
  --set secrets.sessionSecret=$(openssl rand -hex 32) \
  --set ingress.enabled=true

Storage defaults to filesystem-plus-SQLite on one volume — no external services; set config.storage=s3 — plus the s3.* endpoint and bucket values and the S3 credentials under secrets — to put objects in any S3-compatible bucket instead. Realtime lives in-process, so run exactly one replica (the Helm chart refuses to render more). The deploy/README holds the full environment-variable reference and the backup story: the volume is the database — nothing replicates it for you.

The login, demo-mode, and AI sections below apply to a self-hosted instance too — every wrangler secret put becomes the same variable in .env or the Helm values. Subdomains need a wildcard DNS record and TLS pointed at your box instead of Cloudflare's; demo mode's rate-limiting advice is Cloudflare's, so bring your own (usually at the reverse proxy); the cost section stops applying entirely (the cost is the box).

Wildcard subdomains

Serve foo.brisk.example.com by setting the BASE_HOST variable to the host your sites hang off of, then attaching the domain and route in the dashboard (Workers & Pages → your worker → Settings → Domains & Routes):

Don't add these as a routes key in wrangler.jsonc: with routes in the config, wrangler dev rewrites every local request's Host to the production zone, so foo.localhost breaks and the API hands out production URLs from local dev. Dashboard-attached domains persist across deploys, so it's one-time setup.

You also need a wildcard DNS record (*.brisk → CNAME to the apex). The matching TLS certificate is the one part that can cost money — see Cost for the free path (keep sites one level deep, or use a dedicated domain).

Google login

Optional single sign-on for the whole instance: Google OAuth on the apex, with the session cookie scoped to the parent domain so one login covers every site subdomain.

  1. Create an OAuth client (web application) in Google Cloud Console with redirect URI https://brisk.example.com/auth/callback.
  2. Set the secrets:
    npx wrangler secret put GOOGLE_CLIENT_ID
    npx wrangler secret put GOOGLE_CLIENT_SECRET
    npx wrangler secret put SESSION_SECRET     # any long random string
    npx wrangler secret put DEPLOY_TOKEN       # optional, for CI
  3. Set the auth variables in the dashboard: AUTH=google, and restrict who gets in with ALLOWED_EMAIL_DOMAINS=yourco.com for a company, or ALLOWED_EMAILS=you@gmail.com for a personal instance (never allowlist all of gmail.com). Either list admits; both empty admits anyone with a Google account.

Browsers get bounced to Google; the CLI logs in as a real person with brisk login brisk.example.com, and deploys are attributed to that email. The DEPLOY_TOKEN secret is the CI credential (BRISK_TOKEN, shows up as ci@brisk). The default "AUTH": "none" skips all of this and treats every visitor as a trusted dev user — only do that on a network you trust.

Public demo mode

Instances are private by default — everything needs a login. To let strangers see your sites without being able to touch them, set VISIBILITY=public alongside AUTH=google.

The API has no rate limiting, so before publishing spend two minutes in the Cloudflare dashboard on your zone — both are free and stop abusive traffic before it bills as Worker requests:

And leave DEPLOY_TOKEN unset unless you actually have CI — an unset secret means that auth path doesn't exist at all.

AI keys

Set the provider keys as secrets and brisk.ai works on every site with no per-site setup — keys stay on the server.

npx wrangler secret put ANTHROPIC_API_KEY   # and/or OPENAI_API_KEY

Usage bills against your own provider account at their rates — see Cost.

Cost

Brisk is built to run inside Cloudflare's free tier. At personal or small-team scale the whole platform — hosting, database, file storage, and realtime — fits inside it, so it costs nothing. Here's what each piece uses and the free allowance it draws from (per Cloudflare account):

Product Brisk uses it for Free tier
Workers every request to every site and API 100k requests/day, 10 ms CPU/request
R2 site files + brisk.fs uploads 10 GB stored, 1M writes + 10M reads/mo, zero egress
D1 the site registry + brisk.db documents 5 GB, 5M row-reads/day, 100k row-writes/day
Durable Objects one realtime room per site (brisk.channel, db events, presence) included on the free plan (SQLite-backed)

Realtime stays free even idle. Each site's room is a SQLite-backed Durable Object using WebSocket hibernation, so empty rooms bill nothing — you only pay while messages actually flow. And on a public instance, signed-out static views are edge-cached (max-age=300), so demo traffic mostly never reaches R2 or D1 at all.

When you'd cross into paid. 100k requests/day is a lot of headroom for an internal instance. Exceed it and you're on Workers Paid ($5/mo), which turns the daily caps into a forgiving monthly pool (10M requests + 30M CPU-ms included, cheap overage after). At that point R2, D1, and Durable Object usage beyond the included amounts also start metering, but for most instances the $5 base is the whole story.

The one catch is wildcard TLS. Cloudflare's free Universal SSL covers a domain's apex and one level of subdomain. Sites one level deep — foo.example.com, with BASE_HOST=example.com — are covered for free. Nest them under a label — foo.brisk.example.com — and that second-level wildcard needs an advanced certificate (~$10/mo). Two ways around it: register a dedicated domain (~$10/year at Cloudflare Registrar, at cost) and point BASE_HOST at its apex so every *.yourdomain site gets free TLS; or skip subdomains entirely and serve path-mode URLs (/s/foo/), which never need a wildcard.

AI is pass-through. brisk.ai calls bill against the server's own Anthropic or OpenAI key at provider rates — the only cost here with no ceiling, so set a spend limit on the provider side if your sites lean on it.

Bottom line for a personal instance: $0/mo on path mode with no AI, or about $10/year (a dedicated domain) for clean subdomain URLs with free TLS — plus whatever AI tokens your sites spend.