Skip to content

Deploy

Ship Doota with one command — from your machine, or automatically from GitHub on every merge to main.

Updated View as Markdown

You’ve prepared your configuration — now ship it. Doota deploys as one stack: the three Workers plus everything they bind (D1, KV, R2, queues, the Durable Object, the email sender) are declared in infra/alchemy.run.ts, and a single command reconciles your Cloudflare account against that declaration. No manual resource creation, no ID wiring, no secret-by-secret pushes — and required secrets you didn’t provide are minted automatically on the first run.

There are two ways to run that command. Pick one:

Path Best for Auth
A. From your machine First deploy, personal instances, test stages Browser sign-in, no token
B. From GitHub Teams, production — every merge to main deploys itself Three repository secrets

Most people start with A to see their instance come up, then wire up B so production stays current without touching a terminal.

A. Deploy from your machine

1. Sign in to Cloudflare

cd infra
pnpm alchemy login

Opens the browser for OAuth and saves a profile to ~/.alchemy. One-time per machine — no API token needed for this path.

2. Deploy

From the repository root:

pnpm infra:deploy

This builds the web app and deploys the stack. The first run creates everything: database (with the full schema applied), bucket, KV, queues, and the three Workers. When it finishes, the printed webUrl is your running instance:

webUrl: https://doota-dev-yourname.your-account.workers.dev

To preview what a deploy would change without touching anything:

pnpm infra:plan

3. Apply your configuration

If you filled infra/.env during Configuration, it was already applied. Changing config later is the same motion: edit infra/.env, run pnpm infra:deploy again — config re-applies on every deploy, and only the changes go out.

B. Auto-deploy from GitHub

.github/workflows/deploy.yml deploys the prod stage on every push to main — a merged PR is a push, so once this is set up, shipping Doota is just merging. No alchemy login, no .env file in CI; minted secrets come from the state store.

1. Create the deploy token

In the Cloudflare dashboard: My Profile → API Tokens → Create Custom Token, with:

  • Account: Workers Scripts, D1, Workers KV Storage, Workers R2 Storage, Queues, Secrets Store — all Edit; Account Settings Read.
  • Zone (each zone in ORIGINS): Workers Routes Edit, DNS Edit, Zone Read. Skip if you’re workers.dev-only.

This is not the same token as APP_CLOUDFLARE_API_TOKENtwo tokens, two jobs.

2. Add secrets and variables to the repository

Everything lives in one place: your repo on GitHub → Settings → Secrets and variables → Actions. Two tabs matter:

  • Secrets tab → New repository secret — for sensitive values. Write-only once saved; masked in logs.
  • Variables tab → New repository variable — for plain config like URLs.

Add these (names must match exactly — the workflow forwards them as-is):

Name Where Required Value
CLOUDFLARE_API_TOKEN Secret The deploy token from step 1
CLOUDFLARE_ACCOUNT_ID Secret Your account id (dashboard → Workers & Pages → right sidebar)
SETUP_TOKEN Secret Any string ≥ 8 chars you choose — you’ll open /setup with it. The deploy fails without it
APP_CLOUDFLARE_API_TOKEN Secret for mail The app’s runtime token — without it, domain onboarding is off
APP_CLOUDFLARE_ACCOUNT_ID Secret for mail Same account id (the app provisions in it)
ORIGINS Variable for domains Comma-separated full URLs, first canonical — e.g. https://mail.acme.com. Unset → workers.dev
CRON_SECRET, UNSUBSCRIBE_URL, LOG_LEVEL Secret / Variable / Variable Optional features

Nothing else is needed: MAIL_DEK, MAIL_SEARCH_KEY, BETTER_AUTH_SECRET, and the web-push keypair mint themselves on the first CI deploy and persist in the stack’s state store — later runs reuse the same values automatically.

3. Merge to main

That’s the whole deployment procedure from here on. The workflow also has a manual trigger (Actions → Deploy → Run workflow) for re-deploys and the one-time adoption run below.

Coexisting with a wrangler deployment

Already running Doota deployed with wrangler deploy? Nothing changes for it: the stack only ever creates stage-suffixed names, so your bare-name Workers, database, and bucket are never touched — the two deployments live side by side. To move an instance’s data onto a stage, do a manual export/import (D1 export → import, R2 object copy) and provide that deployment’s live secrets via the deploy env so the imported data stays decryptable.

Next: first run

Your Workers are live but no mail flows yet — Email Routing and sending come up per domain. Continue to First run to create the super-admin, onboard a domain, make a mailbox, and send a test message.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close