Self-Hosted State Backends
Persist dashboards, per-user connections, and conversations in your own ClickHouse or Postgres instead of Cloudflare D1 — env vars and resolution order for Docker and Kubernetes deployments.
Self-hosted (Docker / Kubernetes) deployments can persist UI-monitoring state —
saved dashboards, per-user connections, and agent conversations —
in a database you operate, instead of Cloudflare D1 (which only exists on the
hosted Workers deployment). Without a backend, dashboards fall back to browser
localStorage and conversations to memory.
Two self-hosted backends are supported:
- ClickHouse — reuse (or dedicate) a ClickHouse instance via
CHM_STATE_CLICKHOUSE_*. Tables are created lazily (CREATE TABLE IF NOT EXISTS,ReplacingMergeTree) — no migrations to run. - Postgres — via
DATABASE_URL/POSTGRES_URL. Tables are also created lazily on first use.
Resolution order
Each store resolves its backend at runtime, fail-open and OSS-first:
- Explicit backend override, where one exists
(
CONVERSATION_STORE_BACKENDfor conversations) - Cloudflare D1 binding (
CHM_CLOUD_D1, hosted deployment only) - ClickHouse state env (
CHM_STATE_CLICKHOUSE_URLset) — dashboards and connections - Postgres env (
DATABASE_URL/POSTGRES_URL/POSTGRES_PRISMA_URL) - Fallback:
localStorage(dashboards) / memory (conversations)
Server-side dashboard and connection storage additionally requires the
per-user feature gates (CHM_FEATURE_CONVERSATION_DB /
CHM_FEATURE_USER_CONNECTIONS_DB) and an auth provider, since rows are keyed
by user id — see Environment variables.
ClickHouse state backend
CHM_STATE_CLICKHOUSE_URL=http://clickhouse:8123
# Optional
CHM_STATE_CLICKHOUSE_USER=default # default: default
CHM_STATE_CLICKHOUSE_PASSWORD= # default: empty
CHM_STATE_CLICKHOUSE_DATABASE=chmonitor # default: chmonitor
CHM_STATE_CLICKHOUSE_TABLE_PREFIX=chm_state_ # default: chm_state_The user needs CREATE DATABASE / CREATE TABLE, INSERT, SELECT, and
lightweight DELETE permissions on the state database. Tables
(chm_state_dashboards, chm_state_user_connections) use
ReplacingMergeTree(updated_at) with FINAL reads, so updates are plain
inserts and no mutations are required for normal operation.
This can be the same ClickHouse you monitor, but a monitoring user is often
read-only — point CHM_STATE_CLICKHOUSE_USER at a writable account (or a
separate small instance) if so.
Postgres state backend
DATABASE_URL=postgres://user:pass@host:5432/chmonitor
# or POSTGRES_URL / POSTGRES_PRISMA_URLOne connection string serves all three stores (user_connections,
dashboards, and the conversation tables).