Skip to content

Cloudflare Workers

Deploy chmonitor to Cloudflare Workers. Best for globally cached, serverless hosting with no servers to manage.

The dashboard app (apps/dashboard) uses the @cloudflare/vite-plugin to build a native Cloudflare Workers bundle — no OpenNext adapter required. The deploy script is just bun run build && wrangler deploy.

Deploy to Cloudflare

This deploys the apps/dashboard worker (TanStack Start). You will be prompted to connect your Cloudflare account and configure variables. See the environment variable reference below and the dashboard README for the full variable list.

  • Cloudflare account
  • Bun installed locally or in CI
  • CLOUDFLARE_API_TOKEN with Workers deploy permissions (or run wrangler login for OAuth)
Terminal window
git clone https://github.com/duyet/clickhouse-monitoring.git
cd clickhouse-monitoring/apps/dashboard
bun install
## Set credentials and build
export CLICKHOUSE_HOST=https://clickhouse.example.com:8443
## Build the Cloudflare Workers bundle and deploy
bun run cf:deploy # vite build → wrangler deploy

For secrets (ClickHouse password, LLM keys, etc.), use wrangler secret put:

Terminal window
wrangler secret put CLICKHOUSE_HOST
wrangler secret put CLICKHOUSE_PASSWORD

Open your Workers URL or set a custom domain in the Cloudflare dashboard.

There are two kinds of variables:

KindWhere to setWho reads itExample
Build-time client varsCI env / shell before bun run buildBrowser JS (VITE-inlined)VITE_AUTH_PROVIDER, VITE_CLERK_PUBLISHABLE_KEY
Runtime Worker varswrangler.toml [vars] or wrangler secret putWorker process onlyCLICKHOUSE_HOST, LLM_API_KEY, CLERK_SECRET_KEY

VITE_* vars are baked into the JS bundle at build time. Setting them in wrangler.toml [vars] has no effect — they must be in the environment when bun run build runs (e.g. CI secrets).

Runtime Worker vars (wrangler.toml [vars] and secrets) are never visible in the browser.

Non-secret runtime vars go in [vars]:

name = "chmonitor"
main = "@tanstack/react-start/server-entry"
compatibility_date = "2026-05-28"
compatibility_flags = ["nodejs_compat", "nodejs_compat_populate_process_env"]
[observability]
enabled = true
[vars]
CLICKHOUSE_USER = "monitoring"
CLICKHOUSE_NAME = "prod"
CLICKHOUSE_MAX_EXECUTION_TIME = "30"
CLICKHOUSE_TZ = "UTC"
CLICKHOUSE_POOL_SIZE = "10"
CLOUDFLARE_WORKERS = "1"
HEALTH_ALERT_ENABLED = "true"
HEALTH_ALERT_MIN_SEVERITY = "warning"
CONVERSATION_STORE_BACKEND = "d1"
CHM_AUTH_PROVIDER = "none"
LLM_API_BASE = "https://openrouter.ai/api/v1"
LLM_MODEL = "openrouter/free"
AGENT_ENABLE_CONTROL_TOOLS = "false"

Secrets go via wrangler secret put (never in wrangler.toml):

Terminal window
wrangler secret put CLICKHOUSE_HOST
wrangler secret put CLICKHOUSE_PASSWORD
wrangler secret put CLERK_SECRET_KEY
wrangler secret put CHM_PROXY_AUTH_SECRET
wrangler secret put LLM_API_KEY
wrangler secret put CRON_SECRET
wrangler secret put CHM_API_KEY_SECRET

After wrangler secret put, redeploy so the Worker picks up the change: bun run cf:deploy.

Terminal window
wrangler secret put CLICKHOUSE_HOST # https://clickhouse.example.com:8443
wrangler secret put CLICKHOUSE_PASSWORD
[vars]
CLICKHOUSE_USER = "monitoring"
CLICKHOUSE_NAME = "prod"

CLICKHOUSE_HOST defines the host count. CLICKHOUSE_USER and CLICKHOUSE_PASSWORD may be a single value (applied to all hosts) or one value per host position. CLICKHOUSE_NAME is optional:

Terminal window
wrangler secret put CLICKHOUSE_HOST # https://ch1:8443,https://ch2:8443
wrangler secret put CLICKHOUSE_PASSWORD # pass1,pass2
[vars]
CLICKHOUSE_USER = "monitoring,monitoring"
CLICKHOUSE_NAME = "shard-1,shard-2"
[vars]
CLICKHOUSE_MAX_EXECUTION_TIME = "30"
CLICKHOUSE_TZ = "UTC"
CLICKHOUSE_DATABASE = "system"
CLICKHOUSE_POOL_SIZE = "10"
CLICKHOUSE_POOL_TIMEOUT = "300000"
CLICKHOUSE_POOL_CLEANUP_INTERVAL = "60000"

Via wrangler.toml vars:

[vars]
CHM_DISABLED_FEATURES = "peerdb,actions"
CHM_AUTH_REQUIRED_FEATURES = "agent,settings,mcp"
CHM_FEATURE_AGENT_ACCESS = "authenticated"
CHM_FEATURE_SETTINGS_ENABLED = "false"

Via a mounted config file: not directly supported in Workers — use env vars instead.

Feature ids: overview, agent, insights, health, queries, tables, metrics, dashboard, security, logs, settings, cluster, operations, actions, mcp, docs, about.

None (default):

[vars]
CHM_AUTH_PROVIDER = "none"

API key layer:

Terminal window
wrangler secret put CHM_API_KEY_SECRET

Clerk:

Set at build time in CI (these are VITE_* client vars):

Terminal window
## In your CI environment / GitHub Actions secrets:
VITE_AUTH_PROVIDER=clerk
VITE_CLERK_PUBLISHABLE_KEY=pk_live_...

Set the server secret at runtime:

Terminal window
wrangler secret put CLERK_SECRET_KEY # sk_live_...
[vars]
CHM_AUTH_PROVIDER = "clerk"

Proxy — Cloudflare Access (native option):

Put chmonitor behind a Cloudflare Access application. The Worker verifies Cf-Access-Jwt-Assertion JWT from the Access JWKS.

[vars]
CHM_AUTH_PROVIDER = "proxy"
CHM_CF_ACCESS_TEAM_DOMAIN = "https://yourteam.cloudflareaccess.com"
CHM_CF_ACCESS_AUD = "<audience-tag>"

Proxy — trusted header:

[vars]
CHM_AUTH_PROVIDER = "proxy"
CHM_PROXY_AUTH_HEADER = "X-Forwarded-User"
CHM_PROXY_SHARED_SECRET_HEADER = "X-Chm-Proxy-Secret"
Terminal window
wrangler secret put CHM_PROXY_AUTH_SECRET

Without CHM_PROXY_AUTH_SECRET, trusted-header auth is disabled.

Set at runtime via secrets:

Terminal window
wrangler secret put LLM_API_KEY
wrangler secret put AGENT_API_TOKEN
[vars]
LLM_API_BASE = "https://openrouter.ai/api/v1"
LLM_MODEL = "openrouter/free"
AGENT_ENABLE_CONTROL_TOOLS = "false"

Never put LLM_API_KEY in a VITE_* var or [vars] — use wrangler secret put.

Server-side persistence requires VITE_FEATURE_CONVERSATION_DB=true set at build time (in CI before bun run build), plus Clerk auth. The backend is then selected at runtime.

D1 (Cloudflare-native, recommended):

Create a D1 database:

Terminal window
wrangler d1 create chmonitor-conversations

Add the binding to wrangler.toml:

[[d1_databases]]
binding = "CONVERSATIONS_D1"
database_name = "chmonitor-conversations"
database_id = "<database-id-from-above>"
[vars]
CONVERSATION_STORE_BACKEND = "d1"
CONVERSATIONS_D1_DATABASE_ID = "<database-id>"

In CI, also set VITE_FEATURE_CONVERSATION_DB=true before the build step:

Terminal window
## In your CI environment or GitHub Actions secrets:
VITE_FEATURE_CONVERSATION_DB=true

Run migrations:

Terminal window
bun run cf:migrate-conversations

AgentState (cloud-hosted):

[vars]
CONVERSATION_STORE_BACKEND = "agentstate"
Terminal window
wrangler secret put AGENTSTATE_API_KEY

Postgres: also available on Cloudflare Workers via outbound HTTP (DATABASE_URL runtime secret).

Add a Cron Trigger in wrangler.toml:

[triggers]
crons = ["*/5 * * * *"]

The Worker calls GET /api/cron/health-sweep every 5 minutes. Protect the endpoint:

Terminal window
wrangler secret put CRON_SECRET
wrangler secret put HEALTH_ALERT_WEBHOOK_URL
[vars]
HEALTH_ALERT_ENABLED = "true"
HEALTH_ALERT_MIN_SEVERITY = "warning"

Set these in CI before running bun run build (they are baked into the JS bundle):

Terminal window
VITE_TITLE_SHORT=MyCluster
VITE_LOGO=/logo.png
VITE_MEASUREMENT_ID=G-XXXXXXXXXX
VITE_POSTHOG_KEY=phc_...
Terminal window
bun run cf:deploy

This runs: vite build (produces the Cloudflare Workers bundle) → wrangler deploy.

CI (GitHub Actions): push to main triggers .github/workflows/cloudflare.yml. Set CLOUDFLARE_API_TOKEN as a repository secret, plus CLICKHOUSE_*, and any build-time VITE_* vars (e.g. VITE_AUTH_PROVIDER, VITE_CLERK_PUBLISHABLE_KEY).

Terminal window
bun run cf:preview

The app uses these Cloudflare resources (configured in wrangler.toml):

BindingTypePurpose
CONVERSATIONS_D1D1 DatabaseConversation history (optional)
AGENT_CONVERSATIONS_DODurable ObjectConversation history via Durable Objects (optional)

The TanStack Start build via @cloudflare/vite-plugin does not require KV, R2, or cache-tag bindings. Only conversation-store bindings need to be added if you enable server-side persistence.

  1. Pull the latest code: git pull.
  2. Update dependencies: bun install.
  3. Rebuild and deploy: bun run cf:deploy.

Worker secrets persist across deploys; you only need to re-run wrangler secret put when a value changes.

For breaking changes between major versions, see Migrating to v0.3.

wrangler deploy ships nothing or stale assets: run bun run build (or bun run cf:deploy, which does both) before wrangler deploy — the Vite build must produce the Workers bundle first.

Runtime errors / blank pages: check Workers logs in the Cloudflare dashboard. Verify secrets are set (wrangler secret list) and redeploy after any wrangler secret put.

Build-time client var not taking effect: VITE_* vars must be set in the environment when bun run build runs, not in wrangler.toml [vars].