Skip to content
On this page

Self-Hosting

Self-hosting gives you full control over your data and billing while matching the hosted experience.

What you’ll need

  • Git, Bun, and Node.js at the versions pinned in package.json (see the Development guide)
  • A WorkOS account. Teak signs in with WorkOS AuthKit only.
  • Access to secrets for the features you want: Cloudflare R2 and Workers on Workers Paid (file storage, AI, thumbnails, screenshots and media processing), Polar (billing)

Set up WorkOS

Create one WorkOS environment per deployment: a staging environment for local development and a separate production environment. Replace <origin> with the web app’s origin (http://localhost:3000 locally) and <convex-site> with the deployment’s CONVEX_SITE_URL.

  1. In AuthKit, enable email and password sign-in. Add Google or Apple there too if you want them; Teak needs no extra settings for social sign-in.

  2. Under Redirects, add <origin>/callback as a redirect URI, set the sign-in endpoint (initiate login URL) to <origin>/sign-in, and set the sign-out redirect to <origin>/login. Add <origin> as an allowed CORS origin.

  3. Add a JWT template so session tokens carry the claims Teak checks. The backend requires email_verified: true and matches external_id against the account it belongs to. Keys whose value is null are left out, so new users without an external ID still sign in:

    {"email": {{ user.email }}, "email_verified": {{ user.email_verified }}, "external_id": {{ user.external_id }}}
  4. Add a webhook endpoint at <convex-site>/workos/webhook for the user.created, user.updated, and user.deleted events. Copy its signing secret into WORKOS_WEBHOOK_SECRET.

  5. Optional: add a user registration Action at <convex-site>/workos/action so Teak can pause sign-ups (SIGNUPS_DISABLED). Copy its secret into WORKOS_ACTION_SECRET.

  6. From API Keys, copy the client ID and API key into WORKOS_CLIENT_ID and WORKOS_API_KEY, and note the environment ID for WORKOS_ENVIRONMENT_ID.

The webhook and Action routes exist only when WORKOS_ENVIRONMENT_ID is set on the Convex deployment. Without the webhook, new WorkOS users never get a Teak account.

convex.json declares AuthKit redirect URIs, CORS origins, and the app homepage URL under authKit.dev and authKit.prod, and Convex applies them to the deployment’s WorkOS environment. Teak’s own origins are listed there, so edit authKit.prod to your domain before deploying.

Use the WorkOS CLI

The WorkOS CLI can set two of the steps above. Sign in with workos auth login and select the environment first:

# Sign-out redirect (step 2)
workos authkit logout-uris set --uri <origin>/login --default <origin>/login

# Webhook (step 4); the signing secret is shown only in the WorkOS dashboard
workos webhook create --url <convex-site>/workos/webhook \
  --events user.created,user.updated,user.deleted

logout-uris set replaces the environment’s whole list of sign-out URIs.

Connect: CLI, browser extensions, Mac app, Raycast, and MCP

The CLI, browser extensions, Mac app, Raycast, and MCP clients sign in through WorkOS Connect. They need a few more settings.

  1. Turn on Client ID Metadata Document (CIMD) support, and dynamic client registration for MCP clients that don’t support CIMD yet.

  2. Add the resource indicators <origin>/api and <origin>/mcp. These are the deployment’s token audiences, and Teak rejects any other audience. <origin> is WORKOS_RESOURCE_ORIGIN, an HTTPS origin with no path or trailing slash. Unset, it is https://teakvault.com. Set it to your public origin, normally the same as PUBLIC_ORIGIN, so that the MCP resource matches the URL that MCP clients connect to. Clients that require the resource to match the server URL reject any other value.

  3. Create one public OAuth application for each first-party surface, and set its client ID on the Convex deployment:

    Surface Convex variable Redirect URIs
    CLI WORKOS_CONNECT_CLI_CLIENT_ID http://127.0.0.1:14210/oauth/callback, http://127.0.0.1:24210/oauth/callback
    Raycast WORKOS_CONNECT_RAYCAST_CLIENT_ID https://raycast.com/redirect?packageName=Extension
    Chrome extension WORKOS_CONNECT_CHROME_CLIENT_ID https://<extension-id>.chromiumapp.org/oauth/callback
    Firefox extension WORKOS_CONNECT_FIREFOX_CLIENT_ID https://<hash>.extensions.allizom.org/oauth/callback, from browser.identity.getRedirectURL() for the add-on ID. Teak doesn’t publish a Firefox build, but discovery still needs this ID.
  4. Set WORKOS_AUTHKIT_DOMAIN to the environment’s AuthKit domain, an HTTPS origin such as https://your-domain.authkit.app with no path or port.

Sign-in discovery needs WORKOS_AUTHKIT_DOMAIN and all four client IDs. If any of them is missing or malformed, discovery fails and none of these clients can sign in, so set all four even if you only use one.

Quickstart (local)

Clone and run setup

Clone the repo, then run the idempotent bootstrap with your WorkOS staging credentials:

export WORKOS_CLIENT_ID=client_...
export WORKOS_API_KEY=sk_test_...
bun run setup

Setup installs dependencies, provisions an isolated Convex development deployment, sets the local SITE_URL and the WorkOS client ID and API key on it, pushes backend code, and writes the local env files with the Convex URLs for the target you set up (including the AuthKit callback and cookie password for apps/web). No billing or production credentials are required. Verify with bun run doctor.

Configure the rest of WorkOS and optional services

Add the webhook settings from Set up WorkOS and any features you want. Run these from packages/convex:

# Sign-in: webhook and optional sign-up Action
bunx convex env set WORKOS_ENVIRONMENT_ID environment_...
bunx convex env set WORKOS_WEBHOOK_SECRET
bunx convex env set WORKOS_ACTION_SECRET

# Optional: files and AI card processing, through your Files Worker
bunx convex env set FILES_BASE https://files.yourdomain.com
bunx convex env set FILES_SIGNING_SECRET

Leaving the value off makes convex env set prompt for it, which keeps secrets out of your shell history. See Detailed Environment Reference for the full variable list.

Start the stack

bun run dev

Production basics

Designate the administrator

Sign up the intended administrator account first. Then pause new sign-ups and grant its stored admin role from packages/convex:

bunx convex env set --prod SIGNUPS_DISABLED true
bunx convex run --prod admin:seedAdmin '{"email":"[email protected]"}'

Replace the example email with your existing account. The command requires exactly one active account with that email and refuses ambiguous ones. Admin access follows the stored role through email changes; account creation order never grants access. If you paused sign-ups only for this setup, restore their previous setting afterward.

Update SITE_URL

Update SITE_URL in Convex to your public domain (e.g., https://app.yourdomain.com).

Connect the production WorkOS environment

Set up a separate production environment as in Set up WorkOS, using your public origin. Set its WORKOS_* values on the production Convex deployment, and give the web host the same WORKOS_CLIENT_ID and WORKOS_API_KEY plus its own WORKOS_COOKIE_PASSWORD and NEXT_PUBLIC_WORKOS_REDIRECT_URI (https://app.yourdomain.com/callback).

Point clients at production

Update CONVEX_SITE_URL and CONVEX_URL in your client env files to point to your production deployment.

Deploy the Files Worker

Uploads, downloads, thumbnails, and exports all go through the Files Worker. In apps/files-worker/wrangler.jsonc, set bucket_name to your R2 bucket and the route pattern to your files domain. Then, from apps/files-worker:

bunx wrangler secret put FILES_SIGNING_SECRET
bun run deploy

Use the same FILES_SIGNING_SECRET on the Convex deployment, set FILES_BASE there to the worker’s origin, and set NEXT_PUBLIC_FILES_BASE to the same origin when you build the web app.

Host the web app

Point your domain’s SITE_URL to wherever you are hosting the Next.js app.

Add optional services

Add billing (Polar) keys to Convex env if needed. AI, thumbnails, screenshots, and audio and video processing run in the Files Worker.

Build and start

bun run build
bun run start

Detailed Environment Reference

Set these before running locally or deploying.

Backend (Convex deployment)

Set these on the Convex deployment with bunx convex env set from packages/convex, or in the Convex dashboard:

SITE_URL=http://localhost:3000
PUBLIC_ORIGIN=https://yourdomain.com
WORKOS_CLIENT_ID=client_...
WORKOS_API_KEY=sk_test_...
WORKOS_ENVIRONMENT_ID=environment_...
WORKOS_WEBHOOK_SECRET=secret
WORKOS_ACTION_SECRET=secret                  # Optional: sign-up Action
WORKOS_AUTHKIT_DOMAIN=https://your-domain.authkit.app   # Connect sign-in: CLI, extensions, Raycast, MCP
WORKOS_RESOURCE_ORIGIN=https://yourdomain.com # Optional: token audiences <origin>/api and <origin>/mcp (default https://teakvault.com)
WORKOS_CONNECT_CLI_CLIENT_ID=client_...      # Plus the RAYCAST, CHROME, and FIREFOX client IDs
FILES_BASE=https://files.yourdomain.com
FILES_SIGNING_SECRET=shared-secret
POLAR_ACCESS_TOKEN=token
POLAR_ORGANIZATION_TOKEN=token
POLAR_SERVER=sandbox
POLAR_WEBHOOK_SECRET=secret

SITE_URL, WORKOS_CLIENT_ID, and WORKOS_API_KEY are required; sign-in also needs WORKOS_ENVIRONMENT_ID and WORKOS_WEBHOOK_SECRET so new users get a Teak account. Grant admin access through the stored role as described in Production basics. 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, Workers AI, Browser Run (link screenshots), and a media renderer Container, all declared in wrangler.jsonc. Set bucket_name to your own bucket and the route pattern to your files domain.

Deploy it on Workers Paid. Exports stream every file into one ZIP in a single request, which needs the raised cpu_ms and subrequests limits in wrangler.jsonc (Workers Free allows 10 ms of CPU per request). Paid also includes 10 browser hours a month of Browser Run, and Cloudflare Containers, which Workers Free does not offer.

The media renderer runs ffmpeg and poppler in a Container built from apps/files-worker/container/Dockerfile. It makes video thumbnails and hover previews, audio waveforms, PDF thumbnails and PDF text, and converts audio for transcription. wrangler deploy builds the image, so the machine or CI runner that deploys needs Docker. The Container uses the basic instance type (1/4 vCPU, 1 GiB memory) with up to 4 instances (max_instances; keep MEDIA_RENDERER_INSTANCES in vars equal to it). Workers Paid includes 375 vCPU-minutes and 25 GiB-hours of Containers a month, enough for tens of thousands of media jobs. Each instance takes one job at a time, has no internet access, and reads only the file the Worker sends it, up to 100 MB. When the renderer is busy, Teak retries with backoff.

Set the signing secret:

FILES_SIGNING_SECRET=shared-secret           # Must match the Convex value

Deploy it as described in Production basics, 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
WORKOS_CLIENT_ID=client_...                  # Same as the Convex deployment
WORKOS_API_KEY=sk_test_...                   # Same as the Convex deployment
WORKOS_COOKIE_PASSWORD=at-least-32-random-characters
NEXT_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/callback
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.

Browser Extension (apps/extension)

The extension reads the deployment URLs with the VITE_PUBLIC_ prefix:

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

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’s Cloudflare edge forwards https://teakvault.com/api and https://teakvault.com/mcp to them. For self-hosted setups, configure your reverse proxy or DNS to route /api and /mcp to the Convex deployment’s .convex.site domain. The protected-resource metadata advertises your WORKOS_AUTHKIT_DOMAIN as the OAuth issuer.

Where to get the values

  • Convex URLs: Run bunx convex dev. It prints both CONVEX_URL (for the client) and CONVEX_SITE_URL (for the site/auth endpoint). You can also find these in the Convex Dashboard under “Settings” -> “Deployment”.
  • Sign-in:
    • WORKOS_*: From your WorkOS environment, as described in Set up WorkOS. WORKOS_AUTHKIT_DOMAIN is the AuthKit domain shown in the WorkOS dashboard. Sign-in, verification, and password reset emails come from WorkOS.
    • WORKOS_COOKIE_PASSWORD: Generate at least 32 random characters (e.g., via openssl rand -base64 32). Setup generates one locally.
    • SITE_URL: This is the URL where your Next.js app is running. Locally, it’s http://localhost:3000.
  • Optional Features:
    • FILES_BASE + FILES_SIGNING_SECRET: Deploy the Files Worker (see its section above) with a private R2 bucket bound to it; card uploads, downloads, imports, exports, thumbnails, and AI analysis all flow through it. Convex needs no R2 or Workers AI credentials of its own.
    • POLAR_*: Get from Polar if you want to use the built-in billing system.

Last updated on

Was this page helpful?