This guide covers how the operator provisions persistent storage for a
ClickHouseCluster: the primary data volume, attaching extra disks in a
multi-disk (JBOD) layout, expanding capacity, and the rules that govern what you
can and cannot change after a cluster exists.
For the field-by-field reference, see Configuration → Storage configuration and the API Reference.
Primary data volume
spec.dataVolumeClaimSpec is a standard Kubernetes PersistentVolumeClaimSpec.
The operator turns it into a StatefulSet volumeClaimTemplate, so the StatefulSet
controller creates and retains one PersistentVolumeClaim per replica and mounts it
at the ClickHouse data path /var/lib/clickhouse.
apiVersion: clickhouse.com/v1alpha1
kind: ClickHouseCluster
metadata:
name: my-cluster
spec:
dataVolumeClaimSpec:
storageClassName: fast-ssd # optional; depends on the installed CSI driver
resources:
requests:
storage: 100Gi- When
accessModesis omitted, the operator defaults it toReadWriteOnce. - The per-replica PVC is retained when the cluster is deleted, so data survives a delete-and-recreate of the Custom Resource. For data on an encrypted policy this additionally requires preserving the encryption key — see the note in that section.
- The same field exists on
KeeperClusterand behaves the same way.
Running without a persistent data volume
dataVolumeClaimSpec is optional. If you omit it and do not mount your own volume
at the data path, ClickHouse writes to the container’s ephemeral filesystem and the
admission webhook returns a warning that data may be lost if the cluster is restarted.
This is intended only for throwaway or test clusters. To supply your own storage
instead of dataVolumeClaimSpec — for example an emptyDir or a pre-provisioned
volume — define it through spec.podTemplate.volumes and mount it at
/var/lib/clickhouse with spec.containerTemplate.volumeMounts.
Expanding storage
To grow a volume, increase resources.requests.storage and apply the change. The
operator updates the existing PVCs in place.
spec:
dataVolumeClaimSpec:
resources:
requests:
storage: 200Gi # was 100GiMulti-disk (JBOD) storage
spec.additionalVolumeClaimTemplates attaches extra disks to each ClickHouse
replica on top of the primary dataVolumeClaimSpec. Each entry is a named PVC
template — a metadata.name plus a PVC spec — reconciled exactly like the
primary data disk, so the StatefulSet controller creates and retains one PVC per
replica named <name>-<statefulset>-0.
spec:
dataVolumeClaimSpec:
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
additionalVolumeClaimTemplates:
- metadata:
name: disk1
spec:
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
- metadata:
name: disk2
spec:
storageClassName: fast-ssd
resources:
requests:
storage: 100GiThe operator mounts each additional volume at /var/lib/clickhouse/disks/<name>
and generates the ClickHouse storage_configuration for you — you do not write
it by hand. It registers every additional disk and adds it to the built-in default
storage policy.
The primary data disk (default) and every additional disk share a single volume
of the default policy, so ClickHouse spreads new data parts across all of them in
round-robin fashion. Usable capacity is the sum of all disks, and every table that
does not set its own storage_policy — including system.* tables — uses the
combined set.
Custom storage policies
You do not need extraConfig for the JBOD layout above — the operator generates
the default policy automatically. Reach for spec.settings.extraConfig only when
you want storage policies beyond the generated default, for example a tiered
hot/cold policy with move_factor and prefer_not_to_merge, or an S3-backed disk.
Configuration you add there is merged on top of the generated storage_configuration.
See the ClickHouse storage documentation for the policy fields.
At-rest encryption
Setting spec.settings.encryption enables at-rest encryption of table data. The
operator generates a 16-byte AES key — stored in the managed cluster Secret, or
supplied through externalSecret — and a dedicated storage policy that wraps every
data disk with ClickHouse’s encrypted disk type.
spec:
settings:
encryption: {} # enables the feature; the policy defaults to "encrypted"Encryption is opt in per table; the default storage policy stays plaintext. Select the encrypted policy when creating a table:
CREATE TABLE secret_data (id UInt64) ENGINE = MergeTree ORDER BY id
SETTINGS storage_policy = 'encrypted';Set encryption.policyName to use a different policy name.
What you cannot change after creation
Storage layout is largely fixed once a cluster exists. Updates that would orphan data or rebind PersistentVolumeClaims are rejected at admission:
- The presence of
dataVolumeClaimSpecis immutable — you cannot add a data volume to a cluster created without one, nor remove it from a cluster created with one. - The set of
additionalVolumeClaimTemplatesis fixed — you cannot add, remove, or rename entries after creation. - Expanding
resources.requests.storageon an existing entry is allowed (subject to StorageClass support, see Expanding storage). - Encryption cannot be disabled once enabled, and
encryption.policyNamecannot be renamed — tables already using the encrypted policy would become inaccessible.
Validation reference
| Condition | Result |
|---|---|
No dataVolumeClaimSpec and no custom volume at /var/lib/clickhouse |
Warning — possible data loss on restart |
Custom volume mounted at /var/lib/clickhouse while dataVolumeClaimSpec is set |
Rejected |
additionalVolumeClaimTemplates set but dataVolumeClaimSpec missing |
Rejected |
Additional disk named default |
Rejected — reserved by the ClickHouse default disk |
Additional disk name ending in -encrypted |
Rejected — collides with generated encrypted disk names |
Additional disk named clickhouse-storage-volume |
Rejected — collides with the primary data volume name |
| Duplicate additional disk name | Rejected |
Name not matching ^[a-z]([-a-z0-9]*[a-z0-9])?$ or longer than 63 characters |
Rejected by the CRD schema |
Adding or removing dataVolumeClaimSpec after creation |
Rejected |
Adding, removing, or renaming additionalVolumeClaimTemplates after creation |
Rejected |
Reserved volume name in podTemplate.volumes |
Rejected |
encryption.policyName set to default |
Rejected by the CRD schema — the encrypted policy must not replace the default policy |
Disabling encryption or renaming its policy after creation |
Rejected by the CRD schema |
Related guides
- Configuration — the full field reference, including
extraConfig. - Scaling clusters — how replicas and shards are added and removed.