Skip to content
Dirework

Environment variables

All environment variables used by Dirework

Dirework's variables come in two flavors: app secrets the app needs to run, and deploy secrets that only matter when deploying to Cloudflare.

Where they live

EnvironmentWhere to put them
Local developmentpackages/infra/.env — copy it from .env.example. Alchemy loads this file (plus apps/web/.env and apps/server/.env if present) when you run bun run dev.
ProductionGitHub Settings → Secrets and variables → Actions. Sensitive values go under the Secrets tab (CLOUDFLARE_API_TOKEN, ALCHEMY_PASSWORD, ALCHEMY_STATE_TOKEN, BETTER_AUTH_SECRET, TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET); the non-secret origins go under the Variables tab (BETTER_AUTH_URL, CORS_ORIGIN). The deploy workflow reads both on every push to main, and fails fast with a clear message if any are missing.

App secrets

These are what Dirework itself needs to run:

VariableDescription
BETTER_AUTH_SECRETSecret key for session encryption (min 32 characters — generate with openssl rand -base64 32)
BETTER_AUTH_URLThe web app's public origin (local: http://localhost:3001, production: https://dirework.<your-subdomain>.workers.dev)
CORS_ORIGINAllowed CORS origin — same value as BETTER_AUTH_URL
NEXT_PUBLIC_SERVER_URLPublic origin of the API worker, baked into the web build (local: http://localhost:3000). Local only — in production Alchemy injects the API worker's resolved URL itself, so you do not set this anywhere for a deploy.
TWITCH_CLIENT_IDTwitch application client ID from dev.twitch.tv
TWITCH_CLIENT_SECRETTwitch application client secret

In production, BETTER_AUTH_URL and CORS_ORIGIN aren't secrets — set them as GitHub repository Variables (Settings → Secrets and variables → Actions → Variables). The deploy workflow reads them via ${{ vars.* }}. Point both at your web origin — swap in your own workers.dev subdomain.

Don't set NEXT_PUBLIC_SERVER_URL as a GitHub Variable. Alchemy resolves the API worker's URL during the deploy and injects it into both the web build and the runtime binding, so a repository Variable is ignored — it looks load-bearing while doing nothing, and a stale value there is misleading when debugging. It is a local-development variable only.

Deploy secrets

Only needed when deploying to Cloudflare (as GitHub secrets, or in your shell for bun run deploy):

VariableDescription
CLOUDFLARE_API_TOKENCloudflare API token with Workers and D1 edit permissions
ALCHEMY_PASSWORDAny long random string — encrypts Alchemy's deploy state (openssl rand -base64 32)
ALCHEMY_STATE_TOKENAuthenticates the account-wide alchemy-state CI store worker. Generate a unique token for your state-store operator and reuse it only for apps intentionally sharing that same worker. It is not needed for local deploys, which use filesystem state.

Optional variables

VariableDefaultDescription
DOCS_URLhttps://mrdemonwolf.github.io/direworkURL to your documentation site. Used by the !dwhelp chat command to point viewers at the commands page.
PRIVACY_POLICY_URL(unset)URL to your Privacy Policy page. When set, a link appears in the web app's footer.
TERMS_OF_SERVICE_URL(unset)URL to your Terms of Service page. When set, a link appears in the web app's footer.
SKIP_ENV_VALIDATION(unset)Set to "true" during CI or builds to skip env validation when runtime secrets aren't available. You won't need this locally.
DEV_LOGIN(unset)Server worker; local development only. Enables the POST /api/auth/dev-login Twitch-less owner-session bypass. Never set in production.
NEXT_PUBLIC_DEV_LOGIN(unset)Web build var; local development only. Shows the dev-bypass button that calls the dev-login endpoint. Never set in production.

The legal-page URLs are read by the web worker. Set them in apps/web/.env for local development or as GitHub repository Variables for production; the deploy workflow validates and forwards them. If neither is set, the footer's legal links row is hidden.

Access to Dirework is single-owner: the first Twitch account to sign in claims the instance, and no one else can take it over. There's no separate allowlist to configure.

Example packages/infra/.env

# Authentication (min 32 chars — generate with: openssl rand -base64 32)
BETTER_AUTH_SECRET="<your-secret-here>"

# Public URL of the WEB worker (the browser-facing origin)
BETTER_AUTH_URL="http://localhost:3001"

# CORS allowed origin (same as BETTER_AUTH_URL)
CORS_ORIGIN="http://localhost:3001"

# Twitch OAuth (from dev.twitch.tv)
TWITCH_CLIENT_ID="your_client_id_here"
TWITCH_CLIENT_SECRET="your_client_secret_here"

# Documentation site URL (used by the !dwhelp chat command)
# DOCS_URL="http://localhost:4000"

Production notes

For production deployments:

  • Generate fresh secrets for production — never reuse the ones from your computer
  • BETTER_AUTH_URL and CORS_ORIGIN must point at your web worker (https://dirework.<your-subdomain>.workers.dev) — set as GitHub repository Variables, read by the deploy workflow via ${{ vars.* }}
  • Do not set NEXT_PUBLIC_SERVER_URL for production — Alchemy injects the API worker's URL itself (see the warning above)
  • The deploy workflow validates required values, secret lengths, same-origin HTTPS URLs, optional external URLs, and disabled development-login flags before deploying
  • There is no DATABASE_URL — the D1 database is created and bound automatically by Alchemy
  • Update the Twitch OAuth redirect URLs to match your workers.dev domain (see Twitch integration setup)
  • Protect main and require the Verify and CodeQL workflows before merge
  • Enable GitHub secret scanning and push protection
  • Treat bot and overlay URLs as bearer credentials and regenerate them after disclosure

On this page