ClickHouseCluster
Scope: namespaced · Backend: the official ClickHouse operator (type: clickhouse)
ClickHouse for the platform's analytical stores — Langfuse traces and Wäg gateway analytics. Follows the same type × mode shape as the other datastores, and like PostgresCluster it hosts per-consumer logical databases inside one cluster.
Spec
| Field | Type | Description |
|---|---|---|
type | enum clickhouse (default) | Backend: the official ClickHouse operator. altinity is still accepted for existing objects and behaves the same; it never meant the Altinity operator |
mode | enum managed | adopt | external (required) | Provenance mode |
managed | object | Storage, resources, replicas when mode: managed |
adopt | object | Reference to an existing in-cluster ClickHouse CR |
external | object | Connection secret for an external ClickHouse |
databases[] | list | Databases to ensure inside the cluster (one per consuming workload) — see databases[] |
adopt
| Field | Description |
|---|---|
installationRef | The existing ClickHouse installation. namespace is optional and may point at another namespace — the service host is built from it |
credentialsSecretRef | Required in practice: the Secret holding username / password. namespace defaults to the installation's namespace; a Secret outside this CR's namespace is mirrored here as <name>-adopted-credentials |
databases[]
Logical databases to ensure inside the cluster — one per consuming workload, the same idea PostgresCluster.spec.databases already covers for Postgres.
| Field | Description |
|---|---|
name | Database name. Pattern ^[A-Za-z_][A-Za-z0-9_]*$, max 63 characters. ClickHouse identifiers are case-sensitive |
spec:
type: clickhouse
mode: managed
databases:
- name: waegThe operator ensures each entry with CREATE DATABASE IF NOT EXISTS over ClickHouse's HTTP interface and reports the result on status.databases as { name, ready }.
Why this exists
Neither Langfuse nor Wäg creates its own ClickHouse database — both create only their tables. Langfuse gets away with it because it lands in ClickHouse's built-in default, which always exists. Wäg targets a database named waeg and exits at start-up with:
Database waeg does not existSo a Wäg gateway on a fresh managed ClickHouse could not start at all without someone running CREATE DATABASE by hand. Declaring the database here is what makes the cluster usable without that step.
Which modes it applies to
| Mode | Applied? | Why |
|---|---|---|
managed | yes | The operator owns the cluster |
adopt | yes | CREATE DATABASE IF NOT EXISTS is purely additive — it never touches an existing database or its data — and an adopted cluster otherwise cannot host a Wäg gateway |
external | no | The operator was handed a connection string, not ownership. It does not issue DDL against infrastructure it does not manage — create the database yourself |
Databases are never dropped
Removing an entry from spec.databases, deleting the consuming workload, or deleting the ClickHouseCluster CR itself never drops a database — on any path. Analytics and trace history should outlive a mistaken delete. Drop one by hand when you really mean to.
Workloads that reference this cluster
With the auto-wiring licence, databases that referencing workloads use (an Observability's langfuse.clickhouse.databaseName, a Wäg gateway's database) are created without being declared, and status.databases[].claimedBy shows who uses each. See Databases for workloads that reference a datastore.
A Stack declares waeg for you
A Stack with a type: waeg gateway in the default split storage mode adds waeg to its managed ClickHouse automatically — you write nothing. With gateway.waeg.storageMode: single there is no ClickHouse at all, so nothing is declared.
Langfuse's database
A new Stack gives Langfuse its own langfuse database: it is declared here, and the Observability's clickhouse.databaseName points Langfuse at it (CLICKHOUSE_DB, via langfuse-operator 0.10.1 or later). Langfuse waits until this cluster reports the database present.
A Langfuse that already runs on ClickHouse's built-in default — every Stack created before this option existed — stays there. Moving it would start Langfuse on an empty database, and its existing traces would disappear from the UI. To move one deliberately, set clickhouse.databaseName on a standalone Observability and accept that it starts empty (earlier traces stay in default).
Selecting a database by hand
Wiring a consumer yourself? The database is selected with the ?database= query parameter on the ClickHouse HTTP URL — not a path:
http://forge-ch:8123/?database=waeg # correct
http://forge-ch:8123/waeg # silently wrongClickHouse's HTTP interface ignores an unknown path and serves its human-readable landing page, so the second form returns help text instead of an error — which the consumer then reports as an unparseable response, with nothing pointing at the URL.
Behavior by mode
managed— ensures the ClickHouse operator and creates its custom resource. AClickHouseClusterrequires a companionKeeperCluster, which the controller also emits.adopt— references an existing in-cluster ClickHouse CR — possibly in another namespace (cross-namespace adoption) — and publishes its credentials for consumers; lifecycle is not managed. Withoutadopt.credentialsSecretRefnothing can connect, so the resource reportsReady=Falsenaming that field instead of going Ready with no credentials.external— wires a connection secret; no operator installed.databasesis not applied here.
spec.databases is ensured on managed and adopt clusters on every reconcile, and never dropped — see databases[].
Langfuse can manage ClickHouse for you
The langfuse-operator can manage ClickHouse and Redis internally. Unless you reference an external datastore, the Observability controller defaults to operator-managed ClickHouse/Redis — so a dedicated ClickHouseCluster is optional for Langfuse alone. Use this resource when you want to manage or share ClickHouse explicitly — which is also what a type: waeg Gateway in split storage mode needs.
Example — managed
apiVersion: core.navique.com/v1alpha1
kind: ClickHouseCluster
metadata:
name: forge-ch
namespace: forge-data
spec:
type: clickhouse
mode: managedExample — external
spec:
type: clickhouse
mode: external
external:
connectionSecretRef: { name: clickhouse-conn }Backups & restore
Set managed.backup (managed mode only) to schedule backups to object storage. The block is the same provider-neutral shape as Postgres/Mongo — provider: s3 | azure | gcs, a destination, credentials, schedule, and retentionPolicy.
The operator emits an owned <name>-backup CronJob running clickhouse-backup in embedded mode (server-side SQL BACKUP), so it works over the network with the official ClickHouse operator — no sidecar or data-volume access. Setting backup.enabled: false (or removing the block) deletes the CronJob.
spec:
type: clickhouse
mode: managed
managed:
storageSize: 50Gi
backup:
enabled: true
provider: s3
s3:
destinationPath: s3://my-bucket/clickhouse
credentialsSecretRef: { name: ch-backup-creds } # access-key-id / secret-access-key
schedule: "0 0 3 * * *" # 6-field cron; the CronJob uses the 5-field form
retentionPolicy: "30d" # mapped to BACKUPS_TO_KEEP_REMOTERestore is a manual runbook (run from a pod with the same backup env):
clickhouse-backup list remote # find the backup name
clickhouse-backup restore_remote <backup-name> # download + restore via SQLSee the Altinity restore guide.
Status
| Field | Description |
|---|---|
host / port | Connection coordinates for consumers |
credentialsSecret | The Secret carrying username / password in this namespace |
databases[] | One { name, ready } entry per requested database |
phase, conditions, observedGeneration | The standard status surface |