Deployment
Put Dirework online on Cloudflare Workers
Dirework runs on Cloudflare Workers with a D1 (SQLite) database — two small workers and a database, all on Cloudflare's free plan. No servers, no Docker, no database to babysit.
The short version: create a Cloudflare API token, set six GitHub secrets and two required
repository variables, push to main. GitHub Actions deploys both workers and the
database for you.
What gets deployed
| Piece | Where | What it does |
|---|---|---|
dirework | dirework.<your-subdomain>.workers.dev | The web app — dashboard, overlays, bot page |
dirework-api | dirework-api.<your-subdomain>.workers.dev | The API — sign-in, tRPC, bot OAuth |
dirework-db | Cloudflare D1 | Your tasks, timer, and settings |
The web app proxies sign-in and dashboard traffic to the API worker behind the scenes,
so everything works from the one dirework.…workers.dev address.
Before you start
- A Cloudflare account (free plan is fine)
- Your two Twitch keys (client ID + secret)
- A fork or copy of the Dirework repository on GitHub
Step 1 — Create a Cloudflare API token
- Cloudflare dashboard → My Profile → API Tokens → Create Token
- Use the Edit Cloudflare Workers template, and add D1 → Edit permissions
- Copy the token — this is
CLOUDFLARE_API_TOKEN
Step 2 — Tell Twitch about your domain
In the Twitch Developer Console, open your app and add these two redirect URLs (swap in your workers.dev subdomain):
https://dirework.<your-subdomain>.workers.dev/api/auth/callback/twitch
https://dirework.<your-subdomain>.workers.dev/api/bot/callback/twitchYou can keep your localhost URLs in the list too, so local development still works.
Step 3 — Set your GitHub secrets
In your repository: Settings → Secrets and variables → Actions → New repository secret.
| Secret | Value |
|---|---|
CLOUDFLARE_API_TOKEN | the token from Step 1 |
ALCHEMY_PASSWORD | any long random string — encrypts deploy state (openssl rand -base64 32) |
ALCHEMY_STATE_TOKEN | a unique random token (min 32 characters) for the account-wide alchemy-state CI store worker; reuse it only across apps intentionally sharing that same worker |
BETTER_AUTH_SECRET | another long random string (openssl rand -base64 32) |
TWITCH_CLIENT_ID | from the Twitch console |
TWITCH_CLIENT_SECRET | from the Twitch console |
Generate fresh secrets for production — never reuse the ones from your computer.
And set these repository variables
Switch to the Variables tab (same Settings → Secrets and variables → Actions page) → New repository variable. Swap in your own workers.dev subdomain:
| Variable | Value |
|---|---|
BETTER_AUTH_URL | https://dirework.<your-subdomain>.workers.dev (your web origin) |
CORS_ORIGIN | https://dirework.<your-subdomain>.workers.dev (same web origin) |
Optional repository variables are supported too; leave them unset to use the defaults:
| Variable | Purpose |
|---|---|
DOCS_URL | HTTPS documentation origin used by !dwhelp |
PRIVACY_POLICY_URL | HTTPS privacy-policy page linked from the footer |
TERMS_OF_SERVICE_URL | HTTPS terms page linked from the footer |
The deploy workflow reads these via ${{ vars.* }} — you don't edit the workflow file.
It validates every required value, minimum secret lengths, same-origin HTTPS URLs,
optional external URLs, and disabled development-login flags before touching Cloudflare.
Invalid configuration fails before any deployment begins.
There is deliberately no NEXT_PUBLIC_SERVER_URL variable. Alchemy resolves the API
worker's URL during the deploy and injects it into both the web build and the runtime
binding. Setting it as a repository Variable does nothing — and a stale value there is
actively misleading when you're debugging. It's a local-development variable only.
Step 4 — Push to main
That's it. Every push to main runs the full verification pipeline — dependency audit, lint,
type check, tests with coverage thresholds, and a production build — and only then deploys via
Alchemy: both workers, the D1 database, and its migrations.
CodeQL also runs extended static security analysis on pushes, pull requests, and weekly.
You can also trigger a deploy by hand from the Actions tab (Deploy → Run workflow).
✅ Visit https://dirework.<your-subdomain>.workers.dev. You should see the Dirework
welcome screen. Click Claim with Twitch to become the owner.
Deploying from your own machine (optional)
Prefer the command line? Put the same values in packages/infra/.env, then:
bun install
bun run deployTo tear everything down again: bun run destroy.
After you're live — quick checklist
- Both Twitch redirect URLs point at your
dirework.…workers.devdomain - You can sign in and claim the instance
- Your bot account connects (Dashboard → Bot)
- The bot page is added to OBS as a browser source (or pinned in a tab) — the bot only listens to chat while that page is open. See Chat commands.
- The timer and task list overlays show up in OBS
- Exactly one bot page is open; multiple copies can process the same command twice
- Bot and overlay URLs are kept private and regenerated after any suspected disclosure
-
mainis protected and requires the Verify and CodeQL workflows before merge - GitHub secret scanning and push protection are enabled
- GitHub and Cloudflare accounts use MFA and least-privilege access
Free plan limits (you're fine)
Workers' free plan allows 100,000 requests/day. Overlays poll every few seconds only while OBS has them open — a six-hour stream uses roughly 15–25k requests. D1's free tier (5M reads/day) barely notices a single streamer. If you somehow outgrow it, the $5 Workers paid plan raises every limit.