chmonitor

Environment Variables

Full reference for every chmonitor environment variable, grouped by category with build-time vs runtime notes.

Self-hosters need three vars. Everything else is optional and listed here.

CLICKHOUSE_HOST=http://localhost:8123
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=

Copy apps/dashboard/.env.example for a short template. Polar license checkout belongs on landing / apps/cloud-hooks, not the standalone monitor.

One canonical name per setting

A setting the browser needs (auth provider, cloud mode, feature flags) is inlined into the client bundle at build time as a VITE_* var. You do not set it twice. Set the canonical CHM_* name once and vite.config.ts derives the matching client VITE_*:

Set this (canonical)Derives this (client, build-time)
CHM_AUTH_PROVIDERVITE_AUTH_PROVIDER
CHM_CLERK_PUBLISHABLE_KEYVITE_CLERK_PUBLISHABLE_KEY
CHM_CLOUD_MODEVITE_CLOUD_MODE
CHM_FEATURE_CONVERSATION_DBVITE_FEATURE_CONVERSATION_DB
CHM_FEATURE_USER_CONNECTIONS_DBVITE_FEATURE_USER_CONNECTIONS_DB
CHM_EDITIONVITE_EDITION

Precedence per var: explicit VITE_* → canonical CHM_* → legacy NEXT_PUBLIC_* → built-in default. The explicit VITE_* (and legacy NEXT_PUBLIC_*) forms still work as overrides.

A dual-surface setting must still be present at both build time (so vite inlines its VITE_*) and runtime (so the server reads it) — but it is the same single name in both places.

Cloud mode is a build-time contract. CHM_CLOUD_MODE / CHM_DEPLOYMENT_MODE=cloud must be set before the build so VITE_CLOUD_MODE is inlined into the client bundle. Setting it only at runtime on a prebuilt OSS image (e.g. the published Docker image) splits the product: the server enforces cloud behaviour (demo-host guard) while the client still renders OSS UI. /api/healthz detects and warns on this mismatch and reports a cloudMode object. The reverse — a cloud build with the runtime var unset — is safe (fail-closed: both halves degrade to OSS).

Operators set CHM_* (and CLICKHOUSE_* / third-party secrets). Do not also set VITE_*. The build derives client values. Legacy NEXT_PUBLIC_* still works as a fallback — see Migrate to v0.3.

Build-time vs runtime

There are two categories of environment variables:

CategoryWhen resolvedHow to setExample
Build-timeWhen pnpm run build runsCI / shell before the buildCHM_FEATURE_CONVERSATION_DB, CHM_AUTH_PROVIDER, CHM_CLERK_PUBLISHABLE_KEY
RuntimeWhen the server process starts per requestwrangler.toml [vars], wrangler secret put, Docker -e, k8s ConfigMap/SecretCLICKHOUSE_HOST, CONVERSATION_STORE_BACKEND

VITE_* variables are build-time only

Setting a VITE_* var in wrangler.toml [vars] or as a Docker -e flag has no effect on the running app — the bundle already has the value from when it was built. Always rebuild and redeploy when changing these variables.

All other vars (unless noted) are runtime — read from process.env when the Worker or Node process handles a request.

ClickHouse connection

Required for a working dashboard: host, user, password. Name is optional.

VariableRequiredDefaultPurpose
CLICKHOUSE_HOSTYesComma-separated ClickHouse host URLs, e.g. http://ch-1:8123,http://ch-2:8123.
CLICKHOUSE_USERYesdefaultComma-separated usernames, one per host.
CLICKHOUSE_PASSWORDYes""Comma-separated passwords, one per host.
CLICKHOUSE_NAMENoComma-separated display labels shown in the host selector.

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

See Multiple hosts and Custom name.

Query execution

VariableDefaultPurpose
CLICKHOUSE_MAX_EXECUTION_TIME60Query timeout in seconds.
CLICKHOUSE_TZserver defaultTime zone for date/time display.
CLICKHOUSE_DATABASEsystemDatabase used for app-owned tables (self-tracking events, dashboards).
EVENTS_TABLE_NAMEsystem.monitoring_eventsFull table name override for self-tracking events.

Connection pool

VariableDefaultPurpose
CLICKHOUSE_POOL_SIZE10Max concurrent ClickHouse clients per host.
CLICKHOUSE_POOL_TIMEOUT300000Idle client timeout in milliseconds (5 min).
CLICKHOUSE_POOL_CLEANUP_INTERVAL60000Stale-client cleanup interval in milliseconds (1 min).

Health alerting

The cron sweep (GET /api/cron/health-sweep, Cloudflare Cron Trigger every 5 minutes) runs health checks over all hosts and can post webhook alerts without a browser tab open.

VariableDefaultPurpose
CRON_SECRETRequired shared secret guarding the cron endpoints /api/cron/health-sweep and /api/cron/retention-prune. Send as Authorization: Bearer <secret> only (?secret= is rejected). When unset/empty the endpoints fail closed and return HTTP 503 (they do not run) — retention-prune is destructive, so it must not be reachable unauthenticated.
HEALTH_ALERT_ENABLEDfalseSet true to POST webhook alerts. Checks run regardless; alerts only fire when true.
HEALTH_ALERT_WEBHOOK_URLSlack- or Discord-compatible webhook URL. Required for alerts to be dispatched.
HEALTH_ALERT_MIN_SEVERITYwarningMinimum severity to alert: warning (warning + critical) or critical (critical only).
# Alert Slack on warning-or-worse
CRON_SECRET=a-long-random-string
HEALTH_ALERT_ENABLED=true
HEALTH_ALERT_WEBHOOK_URL=https://hooks.slack.com/services/T000/B000/XXXX
HEALTH_ALERT_MIN_SEVERITY=warning

Weekly health report

A second cron job (GET /api/cron/weekly-report) builds a proactive weekly HTML report per opted-in host — a 7-day narrative from the AI insights engine, adaptive statistical baselines, and the disk-capacity forecaster. Opt-in, never opt-out: reports are generated only for hosts listed in CHM_WEEKLY_REPORT_HOSTS.

VariableDefaultPurpose
CHM_WEEKLY_REPORT_HOSTSunset (no reports)Comma-separated host indices to opt in, e.g. 0,2. Uses the same CRON_SECRET guard as health-sweep.
# Opt host 0 and host 2 into the Monday 08:00 UTC weekly report
CRON_SECRET=a-long-random-string
CHM_WEEKLY_REPORT_HOSTS=0,2

Persisted regardless of delivery outcome; best-effort delivered to HEALTH_ALERT_WEBHOOK_URL (above) when configured. See Health — Weekly health report for the report contents and how to view a persisted report.

Prometheus metrics exporter

GET /api/v1/metrics serves system.metrics + system.asynchronous_metrics for every configured host (labeled host="<id>"), plus a chmonitor_alerts_firing gauge from the in-memory alert-dedup state, as Prometheus text exposition format. Cached ~30s; concurrent scrapes share one query batch.

VariableDefaultPurpose
CHM_FEATURE_PROMETHEUS_ENABLEDmode (self-hosted→true, cloud→false)Canonical, server-only (no client bundle exposure needed). Set explicitly to override either default. Never gated on Clerk/billing — disabled returns 404 rather than 403 so cloud never advertises the surface.
# Scrape this instance from Prometheus
CHM_FEATURE_PROMETHEUS_ENABLED=true
# prometheus.yml
scrape_configs:
  - job_name: chmonitor
    scrape_interval: 30s
    metrics_path: /api/v1/metrics
    static_configs:
      - targets: ['dash.example.com']

OTel trace export

Exports chmonitor's OWN query traces — a dashboard-request span per request, with a nested clickhouse-query span per ClickHouse call carrying query_id/read_bytes/host attributes when available — to an external OTLP collector for correlation with the rest of your tracing stack. This is DISTINCT from the built-in OTel span viewer, which reads spans already stored in system.opentelemetry_span_log.

VariableDefaultPurpose
CHM_OTEL_EXPORTER_URLunset (disabled)Collector's OTLP/HTTP traces endpoint. Must be an absolute http(s) URL, including the /v1/traces path. Unset or invalid → trace export stays off: zero spans, zero network calls, zero added latency (fail-open). Server-only, never gated on Clerk/billing.
# Export to a local Jaeger instance (OTLP/HTTP receiver)
CHM_OTEL_EXPORTER_URL=http://jaeger:4318/v1/traces

Runs on Cloudflare Workers: the exporter uses OTLP/HTTP (not gRPC, which Workers cannot run), and the batch export is flushed inline before each response returns rather than deferred with waitUntil (Workers request middleware doesn't expose it here) — so export only adds latency when explicitly enabled.

Feature permissions

All features are public and enabled by default. Override only what needs different behavior.

VariableDefaultPurpose
CHM_CONFIG_FILEPath to TOML or YAML config file for feature overrides.
CHM_DISABLED_FEATURESComma-separated feature ids to disable entirely.
CHM_AUTH_REQUIRED_FEATURESComma-separated feature ids that require authentication.
CHM_FEATURE_{ID}_ACCESSpublicPer-feature access: public, guest (alias for public), or authenticated.
CHM_FEATURE_{ID}_ENABLEDtruePer-feature enabled flag: true or false.

Replace {ID} with an uppercase feature id: OVERVIEW, AGENT, INSIGHTS, HEALTH, QUERIES, TABLES, METRICS, DASHBOARD, SECURITY, LOGS, SETTINGS, BILLING, CLUSTER, OPERATIONS, ACTIONS, MCP, DOCS, ABOUT, PEERDB.

CHM_FEATURE_AGENT_ACCESS=authenticated
CHM_FEATURE_METRICS_ENABLED=false
CHM_DISABLED_FEATURES=settings,insights

See Feature permissions for config-file examples and precedence details.

Rate limiting

Per-IP (and, for the agent, per-signed-in-identity) request throttling on the SQL-executing API surfaces. All four share one implementation (lib/api/rate-limiter.ts, checkRateLimitDurable): an in-memory token bucket everywhere, upgraded to a fleet-wide Cloudflare Rate Limiting binding on Workers deploys (CHM_RATE_LIMIT_API / CHM_RATE_LIMIT_AGENT / CHM_RATE_LIMIT_MCP / CHM_RATE_LIMIT_BROWSER_CONN in wrangler.toml) so the limit holds across isolates, not just per-isolate. Requests over the limit get 429 with a Retry-After header. None of these need to be set for the defaults to apply.

VariableDefaultPurpose
RATE_LIMIT_API_PER_MIN100GET data routes (e.g. /api/v1/charts/$name), per IP.
RATE_LIMIT_AGENT_PER_MIN10POST /api/v1/agent, per IP then tightened per signed-in identity.
RATE_LIMIT_AGENT_GUEST_PER_MIN5Cloud-only: additional per-guest identity bucket on POST /api/v1/agent (agent:guest:guest:<hash>). OSS anonymous stays IP-only.
CHM_GUEST_AI_REQUESTS_PER_DAY3Cloud-only daily AI message cap for anonymous visitors. D1 ai_usage_daily per guest:<ip-hash>. OSS is not gated.
RATE_LIMIT_MCP_PER_MIN30/api/mcp (POST/GET/DELETE), per IP. Same class of capability as the agent route — arbitrary read-only SQL via the query tool — over the MCP transport.
RATE_LIMIT_BROWSER_CONN_PER_MIN10POST /api/v1/browser-connections/{test,sessions}, per IP. Both routes are unauthenticated and dial an attacker-supplied host on every request; each route gets its own bucket so a burst against one doesn't consume the other's budget.

Authentication

The active server-side auth provider is set by CHM_AUTH_PROVIDER. See Authentication for the full model.

VariableDefaultPurpose
CHM_AUTH_PROVIDERnoneServer auth provider: none, clerk, proxy, or trusted. Canonical — the client VITE_AUTH_PROVIDER is derived from it at build time.
VITE_AUTH_PROVIDER(from CHM_AUTH_PROVIDER)Client-side build-time value. Auto-derived from CHM_AUTH_PROVIDER; only set explicitly to override.
CHM_API_KEY_SECRETShared secret for chm_ API keys. When set, API-key auth is always on alongside the active provider. Issue keys at /api/v1/auth/api-key. Required to enable CLI device login.
CHM_DEVICE_LOGINautoCLI device login (chm auth login / /device). auto = on in cloud when CHM_API_KEY_SECRET is set; off for self-hosted/OSS (typical internal network — mint a key once or leave the API open). true = force on (self-hosted opt-in; with CHM_AUTH_PROVIDER=none this is device-only approve — no Clerk, trust reachability of /device). false = force off. Persistence uses D1 when bound, otherwise an in-memory store (single-node).
CHM_DEVICE_LOGIN_SUBJECTself-hostedSubject embedded in chm_ tokens minted via device-only approve (auth=none). Ignored when Clerk/proxy supplies the user id.

Keep secrets server-side

CLERK_SECRET_KEY, CHM_API_KEY_SECRET, and every trust-gate secret below are runtime secrets. Never prefix them with VITE_ or NEXT_PUBLIC_, and never expose them to the browser.

Clerk (CHM_AUTH_PROVIDER=clerk)

VariableDefaultPurpose
CHM_CLERK_PUBLISHABLE_KEYClerk publishable key (pk_..., public). Canonical — the client VITE_CLERK_PUBLISHABLE_KEY is derived from it at build time.
VITE_CLERK_PUBLISHABLE_KEY(from CHM_CLERK_PUBLISHABLE_KEY)Client-side build-time value. Auto-derived; only set explicitly to override. Must be present before pnpm run build.
CLERK_SECRET_KEYClerk secret key (sk_...). Runtime only, secret. Never expose to the browser.
CHM_CLERK_PUBLIC_READfalseWhen true, anonymous visitors can view read-only monitoring content; writes (AI agent, control actions, arbitrary SQL) still require sign-in. Runtime only. See Clerk → public read-only mode.

Reverse proxy (CHM_AUTH_PROVIDER=proxy)

Trust a reverse proxy that already authenticated the user. Either mechanism below can authenticate a request.

Cloudflare Access JWT:

VariableDefaultPurpose
CHM_CF_ACCESS_TEAM_DOMAINCloudflare Access team URL (https://<team>.cloudflareaccess.com). Enables Cf-Access-Jwt-Assertion JWT verification.
CHM_CF_ACCESS_AUDAccess application AUD tag the JWT must carry.

Trusted header (bare subject):

VariableDefaultPurpose
CHM_PROXY_AUTH_SECRETShared secret. When set, the identity header is honored only if this secret is also present (constant-time compare). Without it, the trusted-header mechanism is disabled.
CHM_PROXY_SHARED_SECRET_HEADERX-Chm-Proxy-SecretHeader the proxy sends containing the shared secret.
CHM_PROXY_AUTH_HEADERX-Forwarded-UserHeader the proxy sends containing the authenticated user identity.

Trusted-header gate

When CHM_PROXY_AUTH_SECRET is set, the identity header (X-Forwarded-User by default) is honored only when the request also carries a matching shared-secret header (constant-time compared). Without CHM_PROXY_AUTH_SECRET, the entire trusted-header mechanism is disabled and identity headers are ignored.

Trusted reverse proxy (CHM_AUTH_PROVIDER=trusted)

Use when an upstream proxy (oauth2-proxy, Authelia, Traefik ForwardAuth, nginx auth-url) forwards the user's full identity as HTTP headers. Extracts a complete principal — name, email, avatar, groups, custom claims.

Trust gate — configure exactly one:

VariableDefaultPurpose
CHM_TRUSTED_AUTH_SECRETShared secret (runtime secret). The proxy must send it in CHM_TRUSTED_SHARED_SECRET_HEADER. Constant-time compared. Recommended.
CHM_TRUSTED_ALLOW_INSECUREfalseWhen true, trusts headers with no secret check. Only safe when the service is unreachable except via the proxy (e.g. k8s ClusterIP).
CHM_TRUSTED_SHARED_SECRET_HEADERX-Chm-Proxy-SecretHeader name carrying the shared secret.

Identity headers — all optional; defaults match common proxy conventions:

VariableDefaultPopulates
CHM_TRUSTED_USER_HEADERX-Forwarded-UserSubject / user id. Falls back to the email header.
CHM_TRUSTED_EMAIL_HEADERX-Forwarded-EmailEmail address.
CHM_TRUSTED_NAME_HEADERX-Forwarded-Preferred-UsernameDisplay name.
CHM_TRUSTED_AVATAR_HEADERX-Forwarded-AvatarAvatar URL.
CHM_TRUSTED_GROUPS_HEADERX-Forwarded-GroupsComma- or space-separated groups.
CHM_TRUSTED_ROLE_HEADERX-Forwarded-RoleSingle role; merged into the groups list.
CHM_TRUSTED_CUSTOM_HEADERSExtra claims. Comma-separated field:Header-Name pairs, e.g. team:X-Forwarded-Team.

Access control:

VariableDefaultPurpose
CHM_TRUSTED_ALLOWED_GROUPSComma-separated group names. When set, users whose forwarded groups do not intersect are denied 403. Case-insensitive.

See Trusted proxy for setup examples (oauth2-proxy + Dex + Traefik, nginx).

AI agent / LLM provider

The agent uses an OpenAI-compatible API. Set LLM_API_KEY to enable it. Keep all LLM keys server-side — never prefix them with VITE_.

VariableDefaultPurpose
LLM_API_KEYProvider API key (fallback when no provider-specific key is set).
LLM_API_BASEhttps://openrouter.ai/api/v1OpenAI-compatible API base URL.
LLM_MODELopenrouter:openrouter/freeModel identifier in provider:modelId form.
LLM_EXTRA_MODELSAdditional model picker entries. Format: provider:modelId[|contextLength][|description], comma-separated. Providers: openrouter, nvidia, anyrouter.

Provider-specific keys

Provider-specific keys take priority over the generic LLM_API_KEY / LLM_API_BASE.

VariablePurpose
OPENROUTER_API_KEYOpenRouter API key.
OPENROUTER_API_BASEOpenRouter base URL.
OPENROUTER_REFERERHTTP referer sent to OpenRouter (for rankings).
OPENROUTER_APP_NAMEApp name sent to OpenRouter (for rankings).
OPENROUTER_MODELS_APIOpenRouter models list endpoint (default https://openrouter.ai/api/v1/models).
NVIDIA_API_KEYNVIDIA NIM API key.
NVIDIA_API_BASENVIDIA NIM base URL.
ANYROUTER_API_KEYAnyRouter API key.
ANYROUTER_API_BASEAnyRouter base URL.
ANYROUTER_PRESETS / ANYROUTER_PRESETS_MAXSurface workspace presets in the model picker (on by default, max 8).
ANYROUTER_OAUTH_CLIENT_IDPre-registered OAuth client for "Sign in with AnyRouter" (optional).
OPENROUTER_DYNAMIC_MODELS / OPENROUTER_TOP_MODELS_NLive OpenRouter catalog enrichment (on by default, top 12).

Agent behavior

VariableDefaultPurpose
AGENT_API_TOKENShared Bearer token for the agent API (POST /api/v1/agent). Accepted when CHM_FEATURE_AGENT_ACCESS=authenticated.
AGENT_ENABLE_CONTROL_TOOLSfalseEnable kill-query, optimize, and other write actions. Keep false unless users are trusted.
CHM_AGENT_FIRECRAWL_MCPtrueConnect Firecrawl's hosted MCP (no API key) on every agent request. Set false to disable.
CHM_AGENT_FIRECRAWL_ALLOW_DOMAINS(empty)Comma-separated hostnames Firecrawl may scrape/crawl/map. Empty / unset = unrestricted. Apex tokens also match subdomains; *.example.com is subdomain-only (not the apex). Search-only calls (no URL) stay allowed.

See AI agent — configuration for full details.

Conversation store

Agent conversations default to browser localStorage. Enable server-side persistence with two steps:

Enable the feature (build-time)

Set CHM_FEATURE_CONVERSATION_DB=true (the client VITE_FEATURE_CONVERSATION_DB is derived from it at build time — must be present before pnpm run build). Also requires Clerk auth (CHM_AUTH_PROVIDER=clerk).

Choose a backend (runtime)

Configure a backend at runtime via CONVERSATION_STORE_BACKEND.

VariableDefaultPurpose
CHM_FEATURE_CONVERSATION_DBfalseCanonical. Set true to enable server-side conversation persistence. Requires Clerk. Drives the build-time VITE_FEATURE_CONVERSATION_DB.
VITE_FEATURE_CONVERSATION_DB(from CHM_FEATURE_CONVERSATION_DB)Client-side build-time value. Auto-derived; only set explicitly to override. Must be present before pnpm run build.
CONVERSATION_STORE_BACKEND(auto)Runtime. Backend: agentstate, d1, postgres, or memory. When unset, the server auto-selects the first available backend (AgentState if key present → D1 binding → Postgres via DATABASE_URL → Memory).

AgentState backend:

VariableDefaultPurpose
AGENTSTATE_API_KEYAgentState API key. Required when CONVERSATION_STORE_BACKEND=agentstate.
AGENTSTATE_BASE_URLhttps://agentstate.app/apiAgentState API base URL.
AGENTSTATE_AI_ENRICHfalse (cloud production: true)Set true to enable AI titles and follow-up suggestions.

Cloudflare D1 backend:

VariableDefaultPurpose
CHM_CLOUD_D1_DATABASE_IDCloudflare D1 database UUID for the CHM_CLOUD_D1 binding.
AGENT_CHM_CLOUD_D1_DATABASE_IDAlias for CHM_CLOUD_D1_DATABASE_ID.

PostgreSQL backend:

VariablePurpose
DATABASE_URLPostgreSQL connection string.
POSTGRES_URLAlternative PostgreSQL connection string.
POSTGRES_PRISMA_URLPrisma-specific PostgreSQL connection string.

See Conversation history — backends for setup and fallback behavior.

ClickHouse state backend (self-hosted):

Persists dashboards and per-user connections in your own ClickHouse instead of Cloudflare D1 or Postgres. Opt-in; tables are created lazily (CREATE TABLE IF NOT EXISTS, ReplacingMergeTree). Resolution order per store: D1 binding → ClickHouse state env → Postgres env → local/memory.

VariableDefaultPurpose
CHM_STATE_CLICKHOUSE_URLClickHouse HTTP(S) URL for state storage. Setting it enables the backend.
CHM_STATE_CLICKHOUSE_USERdefaultClickHouse user (needs CREATE/INSERT/SELECT/DELETE on the state database).
CHM_STATE_CLICKHOUSE_PASSWORD(empty)ClickHouse password (secret).
CHM_STATE_CLICKHOUSE_DATABASEchmonitorDatabase holding the state tables.
CHM_STATE_CLICKHOUSE_TABLE_PREFIXchm_state_Prefix for the state tables (chm_state_dashboards, chm_state_user_connections).

See Self-hosted state backends for setup.

User connections (server storage)

Let signed-in users save personal ClickHouse hosts to the server. All four of these must be present together, or the server returns "User connections database storage is not enabled".

VariableDefaultPurpose
CHM_FEATURE_USER_CONNECTIONS_DBfalseCanonical. Set true to enable. Drives the build-time VITE_FEATURE_USER_CONNECTIONS_DB.
VITE_FEATURE_USER_CONNECTIONS_DB(from CHM_FEATURE_USER_CONNECTIONS_DB)Client-side build-time value. Auto-derived; only set explicitly to override.
CHM_AUTH_PROVIDERnoneMust be clerk — connections are keyed by the Clerk userId.
CHM_USER_CONNECTIONS_ENCRYPTION_KEY32-byte base64 key (secret) encrypting saved credentials at rest (AES-256-GCM).

Plus a database backend: the CHM_CLOUD_D1 binding on Cloudflare, or DATABASE_URL / POSTGRES_URL on Docker / Kubernetes.

See User connections for the full setup.

AI insights store

The AI Insights panel on /overview persists its findings through a pluggable store. It is additive opt-in and defaults to ClickHouse; the D1, Postgres, and AgentState backends reuse the conversation-store env above.

VariableDefaultPurpose
INSIGHTS_STORE_BACKENDautoBackend: auto, clickhouse, d1, postgres, agentstate, or memory. auto and clickhouse both use the ClickHouse monitoring_findings table. auto never silently follows other env; a selected backend missing its prerequisite falls back to ClickHouse.
INSIGHTS_D1Optional dedicated D1 binding for the d1 backend. Falls back to CHM_CLOUD_D1 when unset.

postgres reuses DATABASE_URL; agentstate reuses AGENTSTATE_API_KEY (+ optional AGENTSTATE_BASE_URL).

See AI agent — AI Insights persistence for setup and fallback behavior. GET /api/v1/insights/backend reports the active backend.

PeerDB monitoring

Optional. Set PEERDB_API_URL to enable the PeerDB section (Mirrors and Peers) in the sidebar.

VariableDefaultPurpose
PEERDB_API_URLPeerDB REST API base URL. For the PeerDB UI (NextAuth), include /api suffix — e.g. https://peerdb.example.com/api. For the raw flow-api, use the bare origin — e.g. http://localhost:8113.
PEERDB_PASSWORDPeerDB password. Sent as HTTP Basic with an empty username. Server-side only.
PEERDB_CACHE_TTL_MS10000Server-side response cache TTL in milliseconds. Set 0 to disable.
PEERDB_CACHE_MAX_ENTRIES500Max cached PeerDB responses before oldest are evicted.
PEERDB_FETCH_TIMEOUT_MS10000Upstream PeerDB request timeout in milliseconds.

chmonitor proxies only a read-only allowlist of PeerDB endpoints; mutating operations (create/drop/pause, alert config) are rejected with 403.

# PeerDB UI behind NextAuth
PEERDB_API_URL=https://peerdb.example.com/api
PEERDB_PASSWORD=your-peerdb-ui-password

# Raw flow-api (no auth)
PEERDB_API_URL=http://localhost:8113

Analytics & branding

Set the canonical CHM_* names. Client VITE_* is derived at build time.

VariableDefaultPurpose
CHM_ANALYTICS_KEYPostHog project key for opt-in Product Analytics. Empty = disabled.
CHM_ANALYTICS_HOSThttps://us.i.posthog.comPostHog ingest host.

Build-time branding overrides (optional; do not set unless you custom-build): VITE_TITLE_SHORT, VITE_LOGO, VITE_AUTOCOMPLETE_LIMIT, VITE_MEASUREMENT_ID.

CLI client

Env vars for the standalone chm / chmonitor CLI (crate chmonitor). These are read by the binary and scripts/install.sh, not by the dashboard server. Flags override env; env overrides ./chm.toml / ~/.config/chm/config.toml. Default API base is https://dash.chmonitor.dev.

chm auth login discovers how to authenticate by probing public GET /api/v1/auth/cli (method: none | api_key | device) — there is no auth_mode in config. See API keys — device login.

VariableDefaultPurpose
CHM_BASE_URLhttps://dash.chmonitor.devDashboard API origin for hosts / chart / table / tui / auth / …
CHM_CHANNELstableRelease channel for install.sh and chm update: stable or beta. Also --channel / config channel. chm update --beta installs from beta and writes channel = "beta" to the user config (--stable writes stable).
CHM_API_KEYchm_ API key (also --api-key). Used when discovery returns api_key, or alongside device login.
CHM_TOKENBearer token from device login (also --token). Usually stored in the OS keyring.
CHM_HOST_ID0Host index for dashboard API calls (?host=).
CHM_CONFIGPath to a config TOML (overrides the default search paths).
CHM_NO_UPDATE_CHECKSet to 1 to silence the post-command “update available” hint.
CHM_TELEMETRYonSame as dashboard: off / 0 / false / no disables CLI telemetry.
CHM_TELEMETRY_ENDPOINThttps://telemetry.chmonitor.dev/v1/pingEmpty string = hard kill-switch.
DO_NOT_TRACKCross-tool hard override (1) disables CLI telemetry.

Installer (scripts/install.sh)

VariableDefaultPurpose
CHM_CHANNELstablestable skips prereleases; beta prefers chm-v*-beta.N.
CHM_VERSIONlatest for channelPin a release tag (chm-vX.Y.Z, vX.Y.Z, or X.Y.Z).
CHM_INSTALL_DIR$HOME/.local/binInstall directory (never uses sudo).

Server-side device login gates (CHM_DEVICE_LOGIN, CHM_API_KEY_SECRET, …) live under Authentication.

Product telemetry

Anonymous product telemetry is on by default. See Product telemetry.

VariableDefaultPurpose
CHM_TELEMETRYonSet to off (also 0 / false / no) to disable. DO_NOT_TRACK=1 is a hard override.
CHM_TELEMETRY_ENDPOINThttps://telemetry.chmonitor.dev/v1/pingCollection endpoint. Empty string = hard kill-switch (no network).
CHM_LICENSE_KEYOptional. Polar checkout id from a commercial license receipt (the same id lookup accepts). Included on the daily instance ping when telemetry is on. Does not unlock features or fail startup. Do not put a billing email here.

Polar licenses (landing / cloud-hooks)

Self-host license checkout is not configured on the dashboard example. Set Polar product ids and secrets on apps/cloud-hooks (and the hosted .env.production catalog). See apps/cloud-hooks/.env.example. Operators who already bought a license set CHM_LICENSE_KEY on the dashboard process (see Product telemetry above) — not on cloud-hooks.

VariablePurpose
CHM_POLAR_SERVERsandbox or production.
CHM_POLAR_LICENSE_*License SKU product ids (team/unlimited × yearly/lifetime).
POLAR_ACCESS_TOKENOrg access token (secret).
POLAR_WEBHOOK_SECRETWebhook signing secret.

Ops notifications worker (cloud-hooks)

apps/cloud-hooks is the Cloudflare Worker for Polar license checkout, Clerk lifecycle hooks, health-probe digests, and exception/issue-watch notifications. Bindings (CHM_CLOUD_D1, CHM_TELEMETRY_DB, CHM_HOOKS_KV) are declared in apps/cloud-hooks/wrangler.toml, not here. See apps/cloud-hooks/.env.example.

VariablePurpose
POLAR_WEBHOOK_SECRETPolar webhook HMAC signing secret.
POLAR_ACCESS_TOKENPolar org API token for checkout/re-key.
CLERK_SECRET_KEYClerk backend API key (when Clerk hooks are enabled).
CLERK_WEBHOOK_SECRETClerk webhook signing secret (whsec_…).
TELEGRAM_BOT_TOKENTelegram bot token for ops digests/alerts.
TELEGRAM_CHAT_IDDestination chat/channel id for Telegram alerts.
GITHUB_TOKENPAT with issues:write (fallback when GitHub App creds unset).
GH_APP_IDGitHub App id for exception/issue filing (preferred over PAT).
GH_APP_PRIVATE_KEYGitHub App PKCS#8 private key PEM.
GH_APP_INSTALLATION_IDOptional fixed installation id (else resolved from repo).
CF_OBSERVABILITY_API_TOKENCloudflare API token with Workers Observability read.
CHM_POLAR_SERVERsandbox or production Polar API host.
CHM_LICENSE_SUCCESS_ORIGINPost-checkout redirect origin (default https://chmonitor.dev).
CF_ACCOUNT_IDCloudflare account id for observability queries.
GITHUB_REPOSITORYowner/repo for filed issues (default chmonitor/chmonitor).
CHM_EXCEPTION_ISSUE_LABELSComma-separated labels for exception issues.
CHM_EXCEPTION_MAX_ISSUES_PER_RUNMax new exception issues per scan (default 5).
CHM_EXCEPTION_SCRIPTSComma-separated Worker script names to scan.
CHM_ISSUE_WATCH_EXCLUDE_LABELSLabels whose new issues the watch stays quiet about.
CHM_ISSUE_WATCH_MAX_PER_RUNMax new issues announced per ops sweep (default 10).
CHM_POLAR_LICENSE_TEAM_YEARLYPolar product id — team yearly license.
CHM_POLAR_LICENSE_TEAM_LIFETIMEPolar product id — team lifetime license.
CHM_POLAR_LICENSE_UNLIMITED_YEARLYPolar product id — unlimited yearly license.
CHM_POLAR_LICENSE_UNLIMITED_LIFETIMEPolar product id — unlimited lifetime license.

Runtime & build

VariableDefaultPurpose
NODE_ENVdevelopmentRuntime environment: development, production, test.
ENABLE_CLOUDFLAREfalseEnable Cloudflare-specific build configuration.
CLOUDFLARE_WORKERSSet to 1 when running on Cloudflare Workers.
CF_PAGESSet automatically by Cloudflare Pages at runtime.
MINIFLARESet to 1 when running locally with Miniflare.
DOCS_CONTENT_ROOTOverride docs content source directory.

Build-time only

Injected at build time for the About page.

VariablePurpose
VITE_GIT_SHACurrent commit SHA.
VITE_GIT_REFCurrent git branch or tag.
VITE_BUILD_TIMESTAMPISO build timestamp.
VITE_CISet to true in CI environments.

Query config source

VariableDefaultPurpose
CHM_CONFIG_SOURCEtsQuery-config resolution path: ts (hand-written TypeScript configs, current default everywhere) or declarative (opt-in data-only catalog, validated against the same schema and kept at parity — see Catalog contributing). Any value other than the literal string declarative resolves to ts. A malformed declarative entry logs an error and falls back to the ts config rather than crashing.
CHM_CONFIG_DIRECTORY/etc/chmonitor/queries.dSelf-hosted only. Directory scanned once at startup for *.yaml local query-config overrides — no PR or fork needed. Each file must satisfy the same declarative schema as the catalog; an invalid file (bad YAML, schema violation, or duplicate name) is skipped with a warning log, never crashes boot. Checked first, ahead of CHM_CONFIG_SOURCE — a local file overrides a same-named built-in ts or declarative config regardless of that setting. Requires a real filesystem (Docker/K8s; not available on the Cloudflare Worker deploy). Mount example in the repo's docker-compose.yml.
CHM_PACK_REGISTRY_URLComma-separated community query-pack URLs (HTTP(S) or file://), consulted when CHM_CONFIG_SOURCE=declarative. Each pack is a single YAML manifest ({ name, version, queries: [...] }) fetched once at startup (no hot-reload). HTTP(S) fetches are SSRF-guarded; file:// reads a local, operator-chosen path. Precedence (highest to lowest): the self-hosted local queries.d override > packs > the built-in declarative catalog > ts configs. Within packs, the last URL wins on a name collision (logged). An unreachable or malformed pack — or a single bad query inside an otherwise-valid pack — is skipped with a warning; the built-in catalog always still serves.

Legacy / migration

VariableNotes
NEXT_PUBLIC_AUTH_PROVIDERRenamed to VITE_AUTH_PROVIDER in v0.3. Old name still works as a fallback.
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYRenamed to VITE_CLERK_PUBLISHABLE_KEY in v0.3.
NEXT_PUBLIC_FEATURE_CONVERSATION_DBDeprecated alias for VITE_FEATURE_CONVERSATION_DB=true.
NEXT_PUBLIC_AUTOCOMPLETE_LIMITRenamed to VITE_AUTOCOMPLETE_LIMIT in v0.3.
NEXT_PUBLIC_RUNNING_QUERIES_REFRESH_MSRenamed to VITE_RUNNING_QUERIES_REFRESH_MS in v0.3.
CLICKHOUSE_EXCLUDE_USER_DEFAULTComma-separated usernames excluded from history-queries by default.

See Migrate to v0.3 for the full rename list.

On this page