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_PROVIDER | VITE_AUTH_PROVIDER |
CHM_CLERK_PUBLISHABLE_KEY | VITE_CLERK_PUBLISHABLE_KEY |
CHM_CLOUD_MODE | VITE_CLOUD_MODE |
CHM_FEATURE_CONVERSATION_DB | VITE_FEATURE_CONVERSATION_DB |
CHM_FEATURE_USER_CONNECTIONS_DB | VITE_FEATURE_USER_CONNECTIONS_DB |
CHM_EDITION | VITE_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:
| Category | When resolved | How to set | Example |
|---|---|---|---|
| Build-time | When pnpm run build runs | CI / shell before the build | CHM_FEATURE_CONVERSATION_DB, CHM_AUTH_PROVIDER, CHM_CLERK_PUBLISHABLE_KEY |
| Runtime | When the server process starts per request | wrangler.toml [vars], wrangler secret put, Docker -e, k8s ConfigMap/Secret | CLICKHOUSE_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.
| Variable | Required | Default | Purpose |
|---|---|---|---|
CLICKHOUSE_HOST | Yes | — | Comma-separated ClickHouse host URLs, e.g. http://ch-1:8123,http://ch-2:8123. |
CLICKHOUSE_USER | Yes | default | Comma-separated usernames, one per host. |
CLICKHOUSE_PASSWORD | Yes | "" | Comma-separated passwords, one per host. |
CLICKHOUSE_NAME | No | — | Comma-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
| Variable | Default | Purpose |
|---|---|---|
CLICKHOUSE_MAX_EXECUTION_TIME | 60 | Query timeout in seconds. |
CLICKHOUSE_TZ | server default | Time zone for date/time display. |
CLICKHOUSE_DATABASE | system | Database used for app-owned tables (self-tracking events, dashboards). |
EVENTS_TABLE_NAME | system.monitoring_events | Full table name override for self-tracking events. |
Connection pool
| Variable | Default | Purpose |
|---|---|---|
CLICKHOUSE_POOL_SIZE | 10 | Max concurrent ClickHouse clients per host. |
CLICKHOUSE_POOL_TIMEOUT | 300000 | Idle client timeout in milliseconds (5 min). |
CLICKHOUSE_POOL_CLEANUP_INTERVAL | 60000 | Stale-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.
| Variable | Default | Purpose |
|---|---|---|
CRON_SECRET | — | Required 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_ENABLED | false | Set true to POST webhook alerts. Checks run regardless; alerts only fire when true. |
HEALTH_ALERT_WEBHOOK_URL | — | Slack- or Discord-compatible webhook URL. Required for alerts to be dispatched. |
HEALTH_ALERT_MIN_SEVERITY | warning | Minimum 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=warningWeekly 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.
| Variable | Default | Purpose |
|---|---|---|
CHM_WEEKLY_REPORT_HOSTS | unset (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,2Persisted 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.
| Variable | Default | Purpose |
|---|---|---|
CHM_FEATURE_PROMETHEUS_ENABLED | mode (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.
| Variable | Default | Purpose |
|---|---|---|
CHM_OTEL_EXPORTER_URL | unset (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/tracesRuns 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.
| Variable | Default | Purpose |
|---|---|---|
CHM_CONFIG_FILE | — | Path to TOML or YAML config file for feature overrides. |
CHM_DISABLED_FEATURES | — | Comma-separated feature ids to disable entirely. |
CHM_AUTH_REQUIRED_FEATURES | — | Comma-separated feature ids that require authentication. |
CHM_FEATURE_{ID}_ACCESS | public | Per-feature access: public, guest (alias for public), or authenticated. |
CHM_FEATURE_{ID}_ENABLED | true | Per-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,insightsSee 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.
| Variable | Default | Purpose |
|---|---|---|
RATE_LIMIT_API_PER_MIN | 100 | GET data routes (e.g. /api/v1/charts/$name), per IP. |
RATE_LIMIT_AGENT_PER_MIN | 10 | POST /api/v1/agent, per IP then tightened per signed-in identity. |
RATE_LIMIT_AGENT_GUEST_PER_MIN | 5 | Cloud-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_DAY | 3 | Cloud-only daily AI message cap for anonymous visitors. D1 ai_usage_daily per guest:<ip-hash>. OSS is not gated. |
RATE_LIMIT_MCP_PER_MIN | 30 | /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_MIN | 10 | POST /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.
| Variable | Default | Purpose |
|---|---|---|
CHM_AUTH_PROVIDER | none | Server 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_SECRET | — | Shared 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_LOGIN | auto | CLI 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_SUBJECT | self-hosted | Subject 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)
| Variable | Default | Purpose |
|---|---|---|
CHM_CLERK_PUBLISHABLE_KEY | — | Clerk 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_KEY | — | Clerk secret key (sk_...). Runtime only, secret. Never expose to the browser. |
CHM_CLERK_PUBLIC_READ | false | When 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:
| Variable | Default | Purpose |
|---|---|---|
CHM_CF_ACCESS_TEAM_DOMAIN | — | Cloudflare Access team URL (https://<team>.cloudflareaccess.com). Enables Cf-Access-Jwt-Assertion JWT verification. |
CHM_CF_ACCESS_AUD | — | Access application AUD tag the JWT must carry. |
Trusted header (bare subject):
| Variable | Default | Purpose |
|---|---|---|
CHM_PROXY_AUTH_SECRET | — | Shared 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_HEADER | X-Chm-Proxy-Secret | Header the proxy sends containing the shared secret. |
CHM_PROXY_AUTH_HEADER | X-Forwarded-User | Header 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:
| Variable | Default | Purpose |
|---|---|---|
CHM_TRUSTED_AUTH_SECRET | — | Shared secret (runtime secret). The proxy must send it in CHM_TRUSTED_SHARED_SECRET_HEADER. Constant-time compared. Recommended. |
CHM_TRUSTED_ALLOW_INSECURE | false | When 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_HEADER | X-Chm-Proxy-Secret | Header name carrying the shared secret. |
Identity headers — all optional; defaults match common proxy conventions:
| Variable | Default | Populates |
|---|---|---|
CHM_TRUSTED_USER_HEADER | X-Forwarded-User | Subject / user id. Falls back to the email header. |
CHM_TRUSTED_EMAIL_HEADER | X-Forwarded-Email | Email address. |
CHM_TRUSTED_NAME_HEADER | X-Forwarded-Preferred-Username | Display name. |
CHM_TRUSTED_AVATAR_HEADER | X-Forwarded-Avatar | Avatar URL. |
CHM_TRUSTED_GROUPS_HEADER | X-Forwarded-Groups | Comma- or space-separated groups. |
CHM_TRUSTED_ROLE_HEADER | X-Forwarded-Role | Single role; merged into the groups list. |
CHM_TRUSTED_CUSTOM_HEADERS | — | Extra claims. Comma-separated field:Header-Name pairs, e.g. team:X-Forwarded-Team. |
Access control:
| Variable | Default | Purpose |
|---|---|---|
CHM_TRUSTED_ALLOWED_GROUPS | — | Comma-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_.
| Variable | Default | Purpose |
|---|---|---|
LLM_API_KEY | — | Provider API key (fallback when no provider-specific key is set). |
LLM_API_BASE | https://openrouter.ai/api/v1 | OpenAI-compatible API base URL. |
LLM_MODEL | openrouter:openrouter/free | Model identifier in provider:modelId form. |
LLM_EXTRA_MODELS | — | Additional 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.
| Variable | Purpose |
|---|---|
OPENROUTER_API_KEY | OpenRouter API key. |
OPENROUTER_API_BASE | OpenRouter base URL. |
OPENROUTER_REFERER | HTTP referer sent to OpenRouter (for rankings). |
OPENROUTER_APP_NAME | App name sent to OpenRouter (for rankings). |
OPENROUTER_MODELS_API | OpenRouter models list endpoint (default https://openrouter.ai/api/v1/models). |
NVIDIA_API_KEY | NVIDIA NIM API key. |
NVIDIA_API_BASE | NVIDIA NIM base URL. |
ANYROUTER_API_KEY | AnyRouter API key. |
ANYROUTER_API_BASE | AnyRouter base URL. |
ANYROUTER_PRESETS / ANYROUTER_PRESETS_MAX | Surface workspace presets in the model picker (on by default, max 8). |
ANYROUTER_OAUTH_CLIENT_ID | Pre-registered OAuth client for "Sign in with AnyRouter" (optional). |
OPENROUTER_DYNAMIC_MODELS / OPENROUTER_TOP_MODELS_N | Live OpenRouter catalog enrichment (on by default, top 12). |
Agent behavior
| Variable | Default | Purpose |
|---|---|---|
AGENT_API_TOKEN | — | Shared Bearer token for the agent API (POST /api/v1/agent). Accepted when CHM_FEATURE_AGENT_ACCESS=authenticated. |
AGENT_ENABLE_CONTROL_TOOLS | false | Enable kill-query, optimize, and other write actions. Keep false unless users are trusted. |
CHM_AGENT_FIRECRAWL_MCP | true | Connect 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.
| Variable | Default | Purpose |
|---|---|---|
CHM_FEATURE_CONVERSATION_DB | false | Canonical. 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:
| Variable | Default | Purpose |
|---|---|---|
AGENTSTATE_API_KEY | — | AgentState API key. Required when CONVERSATION_STORE_BACKEND=agentstate. |
AGENTSTATE_BASE_URL | https://agentstate.app/api | AgentState API base URL. |
AGENTSTATE_AI_ENRICH | false (cloud production: true) | Set true to enable AI titles and follow-up suggestions. |
Cloudflare D1 backend:
| Variable | Default | Purpose |
|---|---|---|
CHM_CLOUD_D1_DATABASE_ID | — | Cloudflare D1 database UUID for the CHM_CLOUD_D1 binding. |
AGENT_CHM_CLOUD_D1_DATABASE_ID | — | Alias for CHM_CLOUD_D1_DATABASE_ID. |
PostgreSQL backend:
| Variable | Purpose |
|---|---|
DATABASE_URL | PostgreSQL connection string. |
POSTGRES_URL | Alternative PostgreSQL connection string. |
POSTGRES_PRISMA_URL | Prisma-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.
| Variable | Default | Purpose |
|---|---|---|
CHM_STATE_CLICKHOUSE_URL | — | ClickHouse HTTP(S) URL for state storage. Setting it enables the backend. |
CHM_STATE_CLICKHOUSE_USER | default | ClickHouse user (needs CREATE/INSERT/SELECT/DELETE on the state database). |
CHM_STATE_CLICKHOUSE_PASSWORD | (empty) | ClickHouse password (secret). |
CHM_STATE_CLICKHOUSE_DATABASE | chmonitor | Database holding the state tables. |
CHM_STATE_CLICKHOUSE_TABLE_PREFIX | chm_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".
| Variable | Default | Purpose |
|---|---|---|
CHM_FEATURE_USER_CONNECTIONS_DB | false | Canonical. 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_PROVIDER | none | Must be clerk — connections are keyed by the Clerk userId. |
CHM_USER_CONNECTIONS_ENCRYPTION_KEY | — | 32-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.
| Variable | Default | Purpose |
|---|---|---|
INSIGHTS_STORE_BACKEND | auto | Backend: 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_D1 | — | Optional 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.
| Variable | Default | Purpose |
|---|---|---|
PEERDB_API_URL | — | PeerDB 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_PASSWORD | — | PeerDB password. Sent as HTTP Basic with an empty username. Server-side only. |
PEERDB_CACHE_TTL_MS | 10000 | Server-side response cache TTL in milliseconds. Set 0 to disable. |
PEERDB_CACHE_MAX_ENTRIES | 500 | Max cached PeerDB responses before oldest are evicted. |
PEERDB_FETCH_TIMEOUT_MS | 10000 | Upstream 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:8113Analytics & branding
Set the canonical CHM_* names. Client VITE_* is derived at build time.
| Variable | Default | Purpose |
|---|---|---|
CHM_ANALYTICS_KEY | — | PostHog project key for opt-in Product Analytics. Empty = disabled. |
CHM_ANALYTICS_HOST | https://us.i.posthog.com | PostHog 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.
| Variable | Default | Purpose |
|---|---|---|
CHM_BASE_URL | https://dash.chmonitor.dev | Dashboard API origin for hosts / chart / table / tui / auth / … |
CHM_CHANNEL | stable | Release 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_KEY | — | chm_ API key (also --api-key). Used when discovery returns api_key, or alongside device login. |
CHM_TOKEN | — | Bearer token from device login (also --token). Usually stored in the OS keyring. |
CHM_HOST_ID | 0 | Host index for dashboard API calls (?host=). |
CHM_CONFIG | — | Path to a config TOML (overrides the default search paths). |
CHM_NO_UPDATE_CHECK | — | Set to 1 to silence the post-command “update available” hint. |
CHM_TELEMETRY | on | Same as dashboard: off / 0 / false / no disables CLI telemetry. |
CHM_TELEMETRY_ENDPOINT | https://telemetry.chmonitor.dev/v1/ping | Empty string = hard kill-switch. |
DO_NOT_TRACK | — | Cross-tool hard override (1) disables CLI telemetry. |
Installer (scripts/install.sh)
| Variable | Default | Purpose |
|---|---|---|
CHM_CHANNEL | stable | stable skips prereleases; beta prefers chm-v*-beta.N. |
CHM_VERSION | latest for channel | Pin a release tag (chm-vX.Y.Z, vX.Y.Z, or X.Y.Z). |
CHM_INSTALL_DIR | $HOME/.local/bin | Install 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.
| Variable | Default | Purpose |
|---|---|---|
CHM_TELEMETRY | on | Set to off (also 0 / false / no) to disable. DO_NOT_TRACK=1 is a hard override. |
CHM_TELEMETRY_ENDPOINT | https://telemetry.chmonitor.dev/v1/ping | Collection endpoint. Empty string = hard kill-switch (no network). |
CHM_LICENSE_KEY | — | Optional. 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.
| Variable | Purpose |
|---|---|
CHM_POLAR_SERVER | sandbox or production. |
CHM_POLAR_LICENSE_* | License SKU product ids (team/unlimited × yearly/lifetime). |
POLAR_ACCESS_TOKEN | Org access token (secret). |
POLAR_WEBHOOK_SECRET | Webhook 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.
| Variable | Purpose |
|---|---|
POLAR_WEBHOOK_SECRET | Polar webhook HMAC signing secret. |
POLAR_ACCESS_TOKEN | Polar org API token for checkout/re-key. |
CLERK_SECRET_KEY | Clerk backend API key (when Clerk hooks are enabled). |
CLERK_WEBHOOK_SECRET | Clerk webhook signing secret (whsec_…). |
TELEGRAM_BOT_TOKEN | Telegram bot token for ops digests/alerts. |
TELEGRAM_CHAT_ID | Destination chat/channel id for Telegram alerts. |
GITHUB_TOKEN | PAT with issues:write (fallback when GitHub App creds unset). |
GH_APP_ID | GitHub App id for exception/issue filing (preferred over PAT). |
GH_APP_PRIVATE_KEY | GitHub App PKCS#8 private key PEM. |
GH_APP_INSTALLATION_ID | Optional fixed installation id (else resolved from repo). |
CF_OBSERVABILITY_API_TOKEN | Cloudflare API token with Workers Observability read. |
CHM_POLAR_SERVER | sandbox or production Polar API host. |
CHM_LICENSE_SUCCESS_ORIGIN | Post-checkout redirect origin (default https://chmonitor.dev). |
CF_ACCOUNT_ID | Cloudflare account id for observability queries. |
GITHUB_REPOSITORY | owner/repo for filed issues (default chmonitor/chmonitor). |
CHM_EXCEPTION_ISSUE_LABELS | Comma-separated labels for exception issues. |
CHM_EXCEPTION_MAX_ISSUES_PER_RUN | Max new exception issues per scan (default 5). |
CHM_EXCEPTION_SCRIPTS | Comma-separated Worker script names to scan. |
CHM_ISSUE_WATCH_EXCLUDE_LABELS | Labels whose new issues the watch stays quiet about. |
CHM_ISSUE_WATCH_MAX_PER_RUN | Max new issues announced per ops sweep (default 10). |
CHM_POLAR_LICENSE_TEAM_YEARLY | Polar product id — team yearly license. |
CHM_POLAR_LICENSE_TEAM_LIFETIME | Polar product id — team lifetime license. |
CHM_POLAR_LICENSE_UNLIMITED_YEARLY | Polar product id — unlimited yearly license. |
CHM_POLAR_LICENSE_UNLIMITED_LIFETIME | Polar product id — unlimited lifetime license. |
Runtime & build
| Variable | Default | Purpose |
|---|---|---|
NODE_ENV | development | Runtime environment: development, production, test. |
ENABLE_CLOUDFLARE | false | Enable Cloudflare-specific build configuration. |
CLOUDFLARE_WORKERS | — | Set to 1 when running on Cloudflare Workers. |
CF_PAGES | — | Set automatically by Cloudflare Pages at runtime. |
MINIFLARE | — | Set to 1 when running locally with Miniflare. |
DOCS_CONTENT_ROOT | — | Override docs content source directory. |
Build-time only
Injected at build time for the About page.
| Variable | Purpose |
|---|---|
VITE_GIT_SHA | Current commit SHA. |
VITE_GIT_REF | Current git branch or tag. |
VITE_BUILD_TIMESTAMP | ISO build timestamp. |
VITE_CI | Set to true in CI environments. |
Query config source
| Variable | Default | Purpose |
|---|---|---|
CHM_CONFIG_SOURCE | ts | Query-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.d | Self-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_URL | — | Comma-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
| Variable | Notes |
|---|---|
NEXT_PUBLIC_AUTH_PROVIDER | Renamed to VITE_AUTH_PROVIDER in v0.3. Old name still works as a fallback. |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY | Renamed to VITE_CLERK_PUBLISHABLE_KEY in v0.3. |
NEXT_PUBLIC_FEATURE_CONVERSATION_DB | Deprecated alias for VITE_FEATURE_CONVERSATION_DB=true. |
NEXT_PUBLIC_AUTOCOMPLETE_LIMIT | Renamed to VITE_AUTOCOMPLETE_LIMIT in v0.3. |
NEXT_PUBLIC_RUNNING_QUERIES_REFRESH_MS | Renamed to VITE_RUNNING_QUERIES_REFRESH_MS in v0.3. |
CLICKHOUSE_EXCLUDE_USER_DEFAULT | Comma-separated usernames excluded from history-queries by default. |
See Migrate to v0.3 for the full rename list.
Related
Configuration
How the three sources layer and where to set each category.
Settings
In-app settings and their server-side counterparts.
Connection presets
Create a least-privilege read-only ClickHouse user.
Feature permissions
Config-file and env-override details.
Authentication
Choose and configure an auth provider.
Product Analytics
Opt-in, off-by-default PostHog funnel analytics.
Migrate to v0.3
Full rename list from the Next.js era.
Catalog contributing
The declarative query-config catalog CHM_CONFIG_SOURCE opts into.