Skip to content

Self-host (Node / standalone)

Run chmonitor on any server using Node.js or Bun. Best for bare VMs, dedicated servers, or any environment where you manage the process directly.

Terminal window
git clone https://github.com/duyet/clickhouse-monitoring.git
cd clickhouse-monitoring
bun install
## Set your ClickHouse credentials
export CLICKHOUSE_HOST='https://clickhouse.example.com:8443'
export CLICKHOUSE_USER='monitoring'
export CLICKHOUSE_PASSWORD='change-me'
## Build and start
bun run build
bun run start

Open http://localhost:3000.

Put env vars in .env.local in the project root (Next.js) or .env for the TanStack app. Never commit credentials.

Terminal window
cat <<'EOF' > .env.local
CLICKHOUSE_HOST=https://clickhouse.example.com:8443
CLICKHOUSE_USER=monitoring
CLICKHOUSE_PASSWORD=change-me
CLICKHOUSE_NAME=prod
CLICKHOUSE_MAX_EXECUTION_TIME=30
CLICKHOUSE_TZ=UTC
EOF
bun run start
VariableDefaultDescription
CLICKHOUSE_HOST— (required)ClickHouse URL(s), comma-separated
CLICKHOUSE_USERdefaultUsername(s), same count as HOST
CLICKHOUSE_PASSWORD""Password(s), same count as HOST
CLICKHOUSE_NAMEFriendly label(s) for host switcher

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
export CLICKHOUSE_HOST='https://ch1:8443,https://ch2:8443'
export CLICKHOUSE_USER='monitoring,monitoring'
export CLICKHOUSE_PASSWORD='pass1,pass2'
export CLICKHOUSE_NAME='shard-1,shard-2'
VariableDefaultDescription
CLICKHOUSE_MAX_EXECUTION_TIME60Query timeout in seconds
CLICKHOUSE_TZTimezone for queries
CLICKHOUSE_DATABASEsystemDefault database
CLICKHOUSE_POOL_SIZE10Connection pool size
CLICKHOUSE_POOL_TIMEOUT300000Pool acquire timeout (ms)
CLICKHOUSE_POOL_CLEANUP_INTERVAL60000Pool cleanup interval (ms)

Via env vars:

Terminal window
export CHM_DISABLED_FEATURES='peerdb,actions'
export CHM_AUTH_REQUIRED_FEATURES='agent,settings,mcp'
export CHM_FEATURE_AGENT_ACCESS='authenticated'
export CHM_FEATURE_SETTINGS_ENABLED='false'

Via a config file (TOML):

Terminal window
export CHM_CONFIG_FILE='/etc/chmonitor/config.toml'
## /etc/chmonitor/config.toml
[features.agent]
access = "authenticated"
[features.settings]
enabled = false
[features.mcp]
access = "authenticated"

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

None (default):

Terminal window
export CHM_AUTH_PROVIDER='none'

API key layer:

Terminal window
export CHM_API_KEY_SECRET='a-long-random-secret'

Clerk:

Terminal window
export CHM_AUTH_PROVIDER='clerk'
export CLERK_SECRET_KEY='sk_live_...'
## VITE_* vars must be set at build time, not runtime:
## VITE_AUTH_PROVIDER=clerk VITE_CLERK_PUBLISHABLE_KEY=pk_live_... bun run build

Proxy — Cloudflare Access:

Terminal window
export CHM_AUTH_PROVIDER='proxy'
export CHM_CF_ACCESS_TEAM_DOMAIN='https://yourteam.cloudflareaccess.com'
export CHM_CF_ACCESS_AUD='<audience-tag>'

Proxy — trusted header (nginx):

Put chmonitor behind nginx. Configure nginx to set an auth header and pass the secret.

Terminal window
export CHM_AUTH_PROVIDER='proxy'
export CHM_PROXY_AUTH_HEADER='X-Forwarded-User'
export CHM_PROXY_AUTH_SECRET='a-long-random-secret'

Without CHM_PROXY_AUTH_SECRET, the trusted-header provider is disabled. See Authentication.

Example nginx config:

upstream chmonitor {
server 127.0.0.1:3000;
}
server {
listen 443 ssl;
server_name monitor.example.com;
# Set after your auth module populates $remote_user
proxy_set_header X-Forwarded-User $remote_user;
proxy_set_header X-Chm-Proxy-Secret "$chm_proxy_secret"; # inject via env/secrets manager, not hardcoded
location / {
proxy_pass http://chmonitor;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Terminal window
export LLM_API_KEY='sk-...'
export LLM_API_BASE='https://openrouter.ai/api/v1'
export LLM_MODEL='openrouter/free'
export AGENT_API_TOKEN='bearer-token-for-agent-api'
export AGENT_ENABLE_CONTROL_TOOLS='false'

Keep LLM_API_KEY server-side — never set it as a VITE_* variable.

Default: browser localStorage. No server config needed.

Server-side persistence requires VITE_FEATURE_CONVERSATION_DB=true set at build time (a VITE_* build-time variable). Build a custom image or binary with this flag set. At runtime, select the backend:

Postgres store (recommended for self-host):

Terminal window
export CONVERSATION_STORE_BACKEND='postgres'
export DATABASE_URL='postgresql://user:pass@host:5432/dbname'

AgentState store:

Terminal window
export CONVERSATION_STORE_BACKEND='agentstate'
export AGENTSTATE_API_KEY='as_live_...'

D1 and Durable Object stores are Cloudflare-only.

The health sweep runs at GET /api/cron/health-sweep. Call it from a cron job:

Terminal window
export HEALTH_ALERT_ENABLED='true'
export HEALTH_ALERT_WEBHOOK_URL='https://hooks.slack.com/services/...'
export HEALTH_ALERT_MIN_SEVERITY='warning'
export CRON_SECRET='a-random-secret'

Add a crontab entry:

*/5 * * * * curl -sf -H "Authorization: Bearer <CRON_SECRET>" http://localhost:3000/api/cron/health-sweep

Branding vars are inlined at build time:

Terminal window
VITE_TITLE_SHORT=MyCluster VITE_LOGO=/logo.png bun run build

These are inlined at build time — set them before running bun run build.

[Unit]
Description=chmonitor ClickHouse dashboard
After=network.target
[Service]
Type=simple
User=chmonitor
WorkingDirectory=/opt/chmonitor
EnvironmentFile=/etc/chmonitor/env
ExecStart=/usr/local/bin/bun run start
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

Create /etc/chmonitor/env:

Terminal window
CLICKHOUSE_HOST=https://clickhouse.example.com:8443
CLICKHOUSE_USER=monitoring
CLICKHOUSE_PASSWORD=change-me
CHM_AUTH_PROVIDER=none

Enable and start:

Terminal window
systemctl enable --now chmonitor
Terminal window
cd /opt/chmonitor
git pull
bun install
bun run build
systemctl restart chmonitor

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