Skip to content

Cloudflare

GuideMode is built for Cloudflare Workers, and this is how we run the hosted product. On your own account, the server runs at the edge with Cloudflare’s own storage, queues and workflow runtime instead of the Postgres-backed equivalents used elsewhere.

To get the deployment configuration and a walkthrough, contact info@guidemode.dev.

Resource What it is for
Worker The API and the UI. The built UI ships with the Worker as static assets, so there is no second deployment and no bucket to serve from
Hyperdrive Pooled edge connections to your Postgres. We use Neon; any Postgres 17 reachable from Cloudflare works
R2 bucket Session transcripts
KV namespace Rate limits and operation locks
Two queues Session processing and billing updates, each with a producer and a consumer
Eleven Workflows Provider syncs, AI assessment processing and change scoring
A second Worker The notifications Durable Object, bound cross-script for live progress
Cron triggers Nine schedules driving fact refreshes, token renewal, snapshots and maintenance

The notifications Worker deploys before the main Worker, because the main Worker’s binding to it is cross-script and will not resolve otherwise. Database migrations run before either.

Settings reach a Worker two ways. Non-sensitive values live in the Worker configuration as plain variables; everything that is a credential is a secret, set once per Worker and never committed.

These are the plain variables:

NODE_ENV, RELEASE_VERSION, APP_URL, GITHUB_APP_NAME, GOOGLE_REDIRECT_URI, GITLAB_REDIRECT_URI, JIRA_REDIRECT_URI, LINEAR_REDIRECT_URI, PADDLE_ENABLED, PADDLE_CLIENT_TOKEN, the four Paddle price identifiers, AI_PROVIDER and AI_MODELS.

Everything else is a secret:

Terminal window
# Always required
wrangler secret put OAUTH_ENCRYPTION_KEY
wrangler secret put DATABASE_URL
# GitHub App: repository, issue and pull request sync
wrangler secret put GITHUB_APP_ID
wrangler secret put GITHUB_APP_PRIVATE_KEY
wrangler secret put GITHUB_APP_WEBHOOK_SECRET
# Sign-in providers, for the ones you offer
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET
wrangler secret put GOOGLE_CLIENT_ID
wrangler secret put GOOGLE_CLIENT_SECRET
wrangler secret put GITLAB_CLIENT_ID
wrangler secret put GITLAB_CLIENT_SECRET
wrangler secret put MICROSOFT_CLIENT_ID
wrangler secret put MICROSOFT_CLIENT_SECRET
# Work tracking integrations, for the ones you use
wrangler secret put JIRA_CLIENT_ID
wrangler secret put JIRA_SECRET
wrangler secret put LINEAR_CLIENT_ID
wrangler secret put LINEAR_CLIENT_SECRET
wrangler secret put NOTION_CLIENT_ID
wrangler secret put NOTION_CLIENT_SECRET
wrangler secret put NOTION_WEBHOOK_SECRET
wrangler secret put SLACK_CLIENT_ID
wrangler secret put SLACK_CLIENT_SECRET
wrangler secret put SLACK_SIGNING_SECRET
# AI analysis
wrangler secret put GEMINI_API_KEY
wrangler secret put OPENAI_API_KEY
wrangler secret put CLAUDE_API_KEY
wrangler secret put ANTHROPIC_API_KEY
# Email, and optional extras
wrangler secret put RESEND_API_KEY
wrangler secret put GITHUB_PUBLIC_TOKEN
wrangler secret put ADMIN_SECRET
# Billing, only when PADDLE_ENABLED is true
wrangler secret put PADDLE_API_KEY
wrangler secret put PADDLE_WEBHOOK_SECRET
# Tracing, only when LANGFUSE_ENABLED is true
wrangler secret put LANGFUSE_PUBLIC_KEY
wrangler secret put LANGFUSE_SECRET_KEY

Only the first two are mandatory. Every other line belongs to a feature, and a feature whose settings are absent is switched off rather than broken. Where a group is listed together it must be set together, or the Worker will refuse to start and name what is missing.

Two groups from the configuration reference do not apply here. The S3_* group is replaced by the R2 binding, and the JOBS_* settings have no meaning on a runtime with no long-lived process to poll in.

Secrets are per Worker, so a staging Worker needs its own copy of every one of them.

Separately from the Worker’s own settings, whatever runs your deploys needs a Cloudflare API token, your account identifier, and the database URL for the environment being migrated. Those are credentials for the pipeline, not for the application.

A Workflow’s name is unique per Cloudflare account, not per Worker. Whichever script deploys a given name last owns it, and every instance created under that name runs with the owning script’s bindings.

If you run more than one environment on a single account, suffix every Workflow name in the non-production configuration. When names collide the failure is silent and expensive: your production API starts a sync, the run executes against the other environment’s database, and production is left with a sync that never progresses.

Terminal window
wrangler workflows list # name -> owning script

The Worker sets the platform maximum of five minutes of CPU per invocation. This is script-wide rather than per-Workflow, and billing is on CPU actually consumed, so ordinary traffic costs no more for it. Leave it in place: the default of thirty seconds is enough to kill a large repository ingest partway through.

Keep the limit identical across every environment’s configuration. The same class runs in all of them, so a difference means a step that dies in one place and nowhere else.

Deploys run from CI, not a workstation, and promotion is deliberate:

  1. Push a version tag. Migrations run against the staging database, then the staging Worker deploys
  2. Publish a release on that tag. Migrations run against the production database, then the production Worker deploys from the same tag staging validated

The version in the server’s manifest must already match the tag, or the pipeline stops before it touches anything.

Cloudflare is the right choice when you want managed scaling and edge latency, and when your data may live in Cloudflare’s network. If it may not, use Docker or Kubernetes, which run the same product against your own Postgres and object store.