Self-Hosting
Self-hosting gives you full control over your data and billing while matching the hosted experience.
What you’ll need
- Convex CLI (
npm i -g convex) - Access to secrets for the features you want: Cloudflare Workers AI (AI), Kernel (screenshots), Resend (email), Polar (billing)
Quickstart (local)
Clone and run setup
Clone the repo, then run the idempotent bootstrap:
bun run setupSetup installs dependencies, provisions an isolated Convex development deployment, sets the local SITE_URL and JWKS defaults, pushes backend code, and derives apps/web/.env.local — no OAuth or billing credentials required. Verify with bun run doctor.
Start a Convex dev deployment
bunx convex devThis keeps the dev deployment in sync and prints the CONVEX_SITE_URL and CONVEX_URL. It does not configure Better Auth — that’s the next step.
Configure essential secrets
Run these commands to set the minimum required secrets in your Convex dashboard (SITE_URL is already set by setup):
# Security & Auth
npx convex env set BETTER_AUTH_SECRET $(openssl rand -base64 32)
npx convex env set TEAK_ADMIN_EMAIL you@example.com
# Optional: AI (if you want card processing)
npx convex env set CLOUDFLARE_ACCOUNT_ID your-account-id
npx convex env set CLOUDFLARE_API_TOKEN your-api-token
# Optional: Google sign-in (both or neither; email works without them)
npx convex env set GOOGLE_CLIENT_ID your-client-id
npx convex env set GOOGLE_CLIENT_SECRET your-client-secretCreate local env files
Copy the Convex URLs from your dashboard into each app’s .env.local. Each app uses a framework-specific prefix for the same two vars:
| App | Prefix |
|---|---|
apps/web |
NEXT_PUBLIC_ |
apps/mobile |
EXPO_PUBLIC_ |
apps/extension, apps/desktop |
VITE_PUBLIC_ |
See Detailed Environment Reference for the full variable list.
Start the stack
bun run devProduction basics
Designate the administrator
Set TEAK_ADMIN_EMAIL on the production Convex deployment to the normalized email address of the account that should receive administrative access. Teak fails closed when this value is absent or does not match a registered user; account creation order never grants administrative access.
Update SITE_URL
Update SITE_URL in Convex to your public domain (e.g., https://app.yourdomain.com).
Point clients at production
Update CONVEX_SITE_URL and CONVEX_URL in your client env files to point to your production deployment.
Host the web app
Point your domain’s SITE_URL to wherever you are hosting the Next.js app.
Add optional services
Add email (RESEND_API_KEY) and billing (Polar) keys to Convex env if needed.
Build and start
bun run build
bun run startDetailed Environment Reference
Add these to your env files before running locally or deploying.
Backend (packages/convex/.env.local)
SITE_URL=http://localhost:3000
JWKS=null
PUBLIC_ORIGIN=https://yourdomain.com
BETTER_AUTH_SECRET=your-secret
TEAK_ADMIN_EMAIL=you@example.com
FILES_BASE=https://files.yourdomain.com
FILES_SIGNING_SECRET=shared-secret
R2_ACCESS_KEY_ID=access-key
R2_SECRET_ACCESS_KEY=secret-key
R2_ENDPOINT=https://account.r2.cloudflarestorage.com
R2_BUCKET=teak-files
KERNEL_API_KEY=token
CLOUDFLARE_ACCOUNT_ID=your-account-id
CLOUDFLARE_API_TOKEN=your-api-token
POLAR_ACCESS_TOKEN=token
POLAR_ORGANIZATION_TOKEN=token
POLAR_SERVER=sandbox
POLAR_WEBHOOK_SECRET=secret
RESEND_API_KEY=token
SITE_URL is required to boot; BETTER_AUTH_SECRET is required for authentication, while TEAK_ADMIN_EMAIL is only needed for admin access. Add the rest for the features you want. See Where to get the values for sources.
Files Worker (apps/files-worker/.dev.vars, wrangler secrets)
The Files Worker owns all file-byte operations and binds to your R2 bucket plus Cloudflare Images and Workers AI (all declared in wrangler.jsonc — set bucket_name to your own bucket):
FILES_SIGNING_SECRET=shared-secret # Must match the Convex value
Deploy it with bunx wrangler deploy from apps/files-worker, then point FILES_BASE in the backend env at the worker’s public origin.
Web (apps/web/.env.local)
CONVEX_DEPLOY_KEY=
NEXT_PUBLIC_CONVEX_URL=https://deployment.convex.cloud
NEXT_PUBLIC_CONVEX_SITE_URL=https://deployment.convex.site
NEXT_PUBLIC_FILES_BASE=https://files.yourdomain.com # Mirror of FILES_BASE for the CSP
TEAK_DEV_APP_URL=http://localhost:3000 # Optional local override
The web Content-Security-Policy always allows the canonical file origins
(https://files.teakvault.com and the R2 storage origin). If you serve files
from custom origins, mirror the backend FILES_BASE into
NEXT_PUBLIC_FILES_BASE at web build time.
Client Apps (apps/mobile, apps/extension, apps/desktop)
Mobile, Extension, and Desktop share the same base variables (use EXPO_PUBLIC_ prefix for mobile, VITE_PUBLIC_ for extension and desktop):
VITE_PUBLIC_CONVEX_URL=https://deployment.convex.cloud
VITE_PUBLIC_CONVEX_SITE_URL=https://deployment.convex.site
TEAK_DEV_APP_URL=http://localhost:3000 # Optional local override
Desktop-only
The desktop app additionally reads VITE_WEB_URL to know where to send sign-in and account links. Mobile and the extension do not use this variable.
VITE_WEB_URL=https://app.yourdomain.com # Defaults to https://app.teakvault.com
Public API and MCP
PUBLIC_ORIGIN=https://yourdomain.com # Required to expose self-hosted /api and /mcp (else production URLs are advertised)
TEAK_DEV_API_URL=https://deployment.convex.site # Optional local override
Convex serves the REST API at /api/v1, the MCP endpoint at /mcp, plus /healthz, /openapi.json, and OAuth protected-resource metadata. In production, Teak exposes these through apex path rewrites (e.g. https://teakvault.com/api and https://teakvault.com/mcp). For self-hosted setups, configure your reverse proxy or DNS to route /api and /mcp to the Convex deployment’s .convex.site domain. SITE_URL is the Better Auth application origin and the OAuth issuer; there is no separate issuer override.
Where to get the values
- Convex URLs: Run
bunx convex dev. It prints bothCONVEX_URL(for the client) andCONVEX_SITE_URL(for the site/auth endpoint). You can also find these in the Convex Dashboard under “Settings” -> “Deployment”. - Better Auth:
BETTER_AUTH_SECRET: Generate a random string (e.g., viaopenssl rand -base64 32).SITE_URL: This is the URL where your Next.js app is running. Locally, it’shttp://localhost:3000.TEAK_ADMIN_EMAIL: The normalized email address of the one account allowed to use administrative functions. Configure this independently for development and production.
- Optional Features:
R2_*: Create a private Cloudflare R2 bucket and an API token/S3 credentials with object read, write, and delete access.FILES_BASE+FILES_SIGNING_SECRET: Deploy the Files Worker (see its section above); card uploads, downloads, thumbnails, and AI analysis all flow through it.CLOUDFLARE_ACCOUNT_ID+CLOUDFLARE_API_TOKEN: Get from the Cloudflare dashboard — create an API token with Workers AI run permission for AI-powered card processing.KERNEL_API_KEY: Get from Kernel for automated link screenshots.RESEND_API_KEY: Get from Resend for email verification and password resets.POLAR_*: Get from Polar if you want to use the built-in billing system.