> ## Documentation Index
> Fetch the complete documentation index at: https://controlplanecorporation-majid-docs-content-expansion.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# ClickHouse

> Deploy ClickHouse on Control Plane using the Template Catalog. Covers the prerequisite credentials secret, deployment modes, ClickHouse Keeper coordination, object storage on S3, GCS, Azure Blob Storage or Hetzner, and migrating from template version 2.x.

<Warning>
  **Template version 3.0.0 is a breaking change, and one of the changes is a data-loss hazard.**

  * **The template no longer creates a GVC.** It deploys into a GVC you already have. `gvc.name` is gone, and `gvc.locations` moved to a top-level `locations` list.
  * **Never upgrade a 2.x release onto 3.0.0 in place.** A 2.x release owns the GVC it created, and Helm deletes what a chart stops declaring — the upgrade destroys that GVC and everything inside it. Install a new release instead: [Migrating from 2.x](#migrating-from-2-x).
  * **`server.internal_access` and `keeper.internal_access` are now `server.internalAccess` and `keeper.internalAccess`**, and the `workloads` list is finally applied. In 2.x it was silently discarded, so `type: workload-list` blocked everything.
</Warning>

## Overview

ClickHouse is a high-performance, column-oriented analytical database designed for real-time querying and data warehousing at scale. This template deploys ClickHouse in either **single-node** or **cluster** mode depending on how locations are configured, backed by object storage (AWS S3, GCS, Azure Blob Storage, or Hetzner Object Storage) for the table data and a local volume for metadata and fast read caching.

The database password is **not** a template value. ClickHouse reads it from a dictionary secret you create before installing, so it never passes through Helm or lands in the release.

### Deployment Modes

The `locations` list is the topology, not just placement — its length selects the mode, and a location's position in it is that shard's number.

| `locations`                | Mode                                                                      | Keeper                                               |
| -------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------- |
| 1 location, `replicas: 1`  | **Single-node** — development, staging, lower-traffic workloads           | Not deployed                                         |
| 1 location, `replicas` > 1 | **Single-shard cluster** — one shard, N replicas, survives a replica loss | 1 member, so no fault tolerance                      |
| 3 or more locations        | **Multi-shard cluster** — one shard per location                          | 3 members across the first three locations, quorum 2 |
| 2 locations                | **Not supported** — refused at render                                     | —                                                    |

### What Gets Created

* **Stateful ClickHouse Server Workload** — The analytical database itself, with configurable replicas per location.
* **Stateful ClickHouse Keeper Workload** *(cluster modes only)* — The Raft coordination service, one replica in each of the first three locations.
* **Volume Sets** — Persistent storage for the server (metadata, `store/` and system files) and, in cluster modes, for Keeper. Table data lives in object storage; the volume is metadata and read cache.
* **Scratch Volumes** — Local filesystem cache and temporary spill.
* **Secrets** — Startup script secrets for ClickHouse Server and Keeper, and a storage configuration secret for the selected provider. **No credential secret** — the password lives only in the prerequisite secret you create.
* **Identity & Two Policies** — An identity bound to the workloads, with `reveal` on the template's own secrets plus exactly the secrets you created, `view` on the one GVC you install into so each container can confirm at boot that the GVC really has every location you listed, and cloud access to the bucket when the provider is AWS.

<Note>
  **This template does not create a GVC.** It deploys into a GVC you already have — every resource lands in the GVC you install into, so `cpln workload exec`, `cpln logs` and uninstalling all work against that GVC, and uninstalling can never delete it. Every location you list in `locations` must already be on that GVC, and a GVC location you did not list simply runs nothing.
</Note>

### Architecture

In multi-shard mode, each of the first three locations runs one ClickHouse Keeper replica, forming a 3-node quorum for distributed coordination. ClickHouse Server replicas reach Keeper over Control Plane's internal DNS. In single-node mode, no Keeper is deployed. Primary data is stored in the configured object storage bucket in every mode; a local scratch volume serves as a fast read cache.

<Note>
  To minimize network egress costs, deploy all locations in the same cloud provider and keep your object storage bucket in the same region family. One server replica per location is enough for most cluster deployments.
</Note>

## Prerequisites

Three things must be in place before you install: a GVC with the right locations, a credentials secret, and object storage access for your chosen provider.

### A GVC with your locations

**A GVC must already exist, and it must contain every location you list in `locations`.** The requirement is one-directional — the GVC may have *more* locations than you list, and nothing ClickHouse-related runs in those. Check what a GVC has before installing:

```bash theme={null}
cpln gvc get GVC_NAME -o json
```

The locations are under `spec.staticPlacement.locationLinks`. To add a missing one:

```bash theme={null}
cpln gvc add-location GVC_NAME --location aws-us-east-2
```

Every workload in a GVC runs in every location that GVC has, so add locations to a shared GVC deliberately.

<Warning>
  **A location the GVC does not have is not caught at install time.** The install succeeds — the platform does not validate it — and the containers then refuse to initialize, restarting with this in the logs:

  ```text theme={null}
  [clickhouse] FATAL: locations declared in values are not in GVC 'my-gvc': aws-eu-central-1
  [clickhouse] GVC 'my-gvc' has: aws-us-east-1 aws-us-east-2
  ```

  A node that is **already initialized** logs a `WARNING` instead and keeps serving, so this check can never take down a running cluster.

  If **every** location you list is absent from the GVC, nothing starts at all and there is no container to log anything. `cpln workload get-deployments` then shows zero replicas in every location, `"desiredScale": 0`, and the message `This workload location is deactivated because maxScale is set to 0.` — that message, not the field, is the signal to look for.
</Warning>

### Database credentials

**One secret must exist before you install.** It holds the password every ClickHouse client connection uses, so it is not a value — a value would leave it in the Helm release.

<Steps>
  <Step title="Create the credentials secret">
    A [dictionary secret](/guides/create-secret/dictionary) holding exactly two keys — `password` and `database`:

    ```bash theme={null}
    cpln secret create-dictionary --name my-clickhouse-credentials \
      --entry password='YOUR-STRONG-PASSWORD' \
      --entry database=mydatabase
    ```

    Set `database.credentialsSecretName` to the name you used. Secret names are org-wide, so give each release its own.
  </Step>

  <Step title="Read the secret back later">
    Pass `-o yaml`. A bare `cpln secret reveal` prints only a summary table, not the values:

    ```bash theme={null}
    cpln secret reveal my-clickhouse-credentials -o yaml
    ```
  </Step>
</Steps>

<Note>
  **There is no `username` key.** ClickHouse authenticates as its built-in `default` user here, so the secret holds only the password and the database name.
</Note>

<Warning>
  **Create the secret before installing, or the deployment wedges silently.** The template refuses to render when `database.credentialsSecretName` is blank, but a name pointing at a secret that does not exist installs "successfully" and then never starts. The container never runs, so `cpln logs` returns **zero lines** — there is nothing to log, and every summary surface just looks like a slow deploy. The one place the reason appears is `status.versions[].message`:

  ```bash theme={null}
  cpln workload get-deployments RELEASE_NAME-clickhouse-server --gvc GVC_NAME -o yaml
  ```

  ```text theme={null}
  The secret my-clickhouse-credentials no longer exists. Workload updates are paused until the
  secret is added or the reference to the secret removed.
  ```

  Use `get-deployments` — plain `cpln workload get` has no `versions` key and will show you nothing. Creating the missing secret repairs it on its own with no further action — measured at 9 minutes 12 seconds on this template, within the roughly **5.5 to 10.5 minute** band seen across the catalog — or run `cpln workload force-redeployment RELEASE_NAME-clickhouse-server --gvc GVC_NAME` to clear it in about 90 seconds.
</Warning>

### Object storage

Object storage is required in **every** deployment mode, including single-node — there is no local-only shape. Choose one provider and complete its setup below. AWS is the only keyless option: access comes from a Cloud Account through the workload identity, so there is no key to store. The other three each need their own prerequisite dictionary secret.

#### AWS S3

1. Create an S3 bucket. Set `aws.bucket` to its name and `aws.region` to its region.

2. If you do not have a Control Plane Cloud Account set up, follow the [Create a Cloud Account](/guides/create-cloud-account) guide. Set `aws.cloudAccountName` to its name.

3. Create an IAM policy with the following JSON, replacing `YOUR_BUCKET_NAME`, and set `aws.policyName` to its name:

```json theme={null}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:ListBucket",
                "s3:GetObjectVersion",
                "s3:DeleteObjectVersion",
                "s3:GetBucketLocation",
                "s3:AbortMultipartUpload",
                "s3:ListBucketMultipartUploads",
                "s3:ListMultipartUploadParts"
            ],
            "Resource": [
                "arn:aws:s3:::YOUR_BUCKET_NAME",
                "arn:aws:s3:::YOUR_BUCKET_NAME/*"
            ]
        }
    ]
}
```

#### GCS

ClickHouse reaches GCS over its S3-compatible interface, which requires an interoperability HMAC key. A Cloud Account is not required.

1. Create a GCS bucket. Set `gcp.bucket` to the bucket name.

2. In the GCP console, navigate to **Settings > Interoperability** and click **Create a key for a service account**.

3. Click **Create new account**, name your service account, and assign the **Storage Object Admin** role under Permissions.

4. Store the generated HMAC key in a dictionary secret, and set `gcp.credentialsSecretName` to that secret's name:

```bash theme={null}
cpln secret create-dictionary --name my-clickhouse-gcs-credentials \
  --entry accessKeyId=YOUR_HMAC_ACCESS_KEY \
  --entry secretAccessKey=YOUR_HMAC_SECRET
```

Alternatively, use the `gcloud` CLI:

```bash theme={null}
gcloud config set project YOUR_PROJECT_ID

gcloud storage buckets create gs://YOUR_BUCKET_NAME --location=NAM4

gcloud iam service-accounts create clickhouse-storage

gcloud projects add-iam-policy-binding $(gcloud config get-value project) \
  --member="serviceAccount:clickhouse-storage@$(gcloud config get-value project).iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

gsutil hmac create clickhouse-storage@$(gcloud config get-value project).iam.gserviceaccount.com
```

#### Azure Blob Storage

ClickHouse uses Azure's native Blob Storage SDK. A Cloud Account is not required — authentication uses a storage account access key.

1. In the [Azure Portal](https://portal.azure.com), go to **Storage accounts → Create**. Use **Standard** performance, **LRS** redundancy, and leave hierarchical namespace off.

2. Inside the storage account, go to **Containers → + Container** and create a container (e.g. `clickhouse-data`). Set access level to **Private**. Set `azure.storageAccount` and `azure.container`.

3. Go to **Security + networking → Access keys** and copy either `key1` or `key2`.

4. Store the key in a dictionary secret, and set `azure.credentialsSecretName` to that secret's name:

```bash theme={null}
cpln secret create-dictionary --name my-clickhouse-azure-credentials \
  --entry accountKey=YOUR_ACCOUNT_KEY
```

Alternatively, use the Azure CLI:

```bash theme={null}
az storage account create \
  --name YOUR_STORAGE_ACCOUNT \
  --resource-group YOUR_RESOURCE_GROUP \
  --sku Standard_LRS

az storage container create \
  --name clickhouse-data \
  --account-name YOUR_STORAGE_ACCOUNT

az storage account keys list \
  --account-name YOUR_STORAGE_ACCOUNT \
  --resource-group YOUR_RESOURCE_GROUP \
  --query "[0].value" -o tsv
```

#### Hetzner Object Storage

Hetzner Object Storage is S3-compatible. A Cloud Account is not required — authentication uses an access key pair.

Available regions:

| Region | Location             |
| ------ | -------------------- |
| `nbg1` | Nuremberg, Germany   |
| `hel1` | Helsinki, Finland    |
| `fsn1` | Falkenstein, Germany |

1. In the Hetzner Cloud console, go to **Object Storage** and create a bucket. Set `hetzner.bucket` and `hetzner.region`.

2. Go to **Security → S3 Credentials** and click **Generate credentials**. Save the access key and secret key immediately — the secret will not be shown again.

3. Store the pair in a dictionary secret, and set `hetzner.credentialsSecretName` to that secret's name:

```bash theme={null}
cpln secret create-dictionary --name my-clickhouse-hetzner-credentials \
  --entry accessKeyId=YOUR_ACCESS_KEY \
  --entry secretAccessKey=YOUR_SECRET_KEY
```

## Installation

Create the [prerequisite secrets](#database-credentials) first, then install by whichever method you prefer:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Migrating from 2.x

Template versions through 2.8.0 created their own GVC, so that GVC is part of the 2.x release's manifest. Version 3.0.0 does not declare it — and Helm deletes what a chart stops declaring.

<Warning>
  **An in-place upgrade from 2.x to 3.0.0 destroys the deployment.** Measured on a sibling template with the guard removed: the upgrade **deleted the GVC and every workload, volume set and identity inside it in about six seconds, while printing `upgraded successfully`.** Reading the GVC back afterwards returned `404`. The volume sets hold your ClickHouse metadata and Keeper state.

  The chart ships a render-time refusal so this cannot happen by accident: any leftover `gvc` key in your values aborts the upgrade before a single API call is made, leaving your cluster untouched and running.

  **The guard cannot cover one case: an upgrade run with no values file at all.** A 2.x release installed on pure defaults has no `gvc` key for the chart to see, so nothing fires and the deletion proceeds. Do not run an upgrade of a 2.x release against the 3.0.0 chart under any circumstances — install a new release and re-ingest.
</Warning>

<Steps>
  <Step title="Recover the password the existing cluster uses">
    Versions up to 2.5.0 took the password as a plain Helm value and wrote it into a chart-owned secret named after the release. Read it out of your current values file, or out of that secret:

    ```bash theme={null}
    cpln secret reveal RELEASE_NAME-clickhouse-db-config -o yaml
    ```

    Pass `-o yaml`. A bare `cpln secret reveal` prints only a summary table, not the values. Any password that came from a pre-2.6.0 default was published in the public template repository — treat it as compromised and choose a new one for the new deployment.
  </Step>

  <Step title="Choose the GVC for the new release">
    Create or pick a GVC and make sure it has exactly the locations you intend to list in `locations`. See [Prerequisites](#prerequisites).
  </Step>

  <Step title="Install 3.0.0 as a NEW release into that GVC">
    Use a **different release name** — secret names are org-wide, so a same-named release collides with the 2.x one even in another GVC. Point the new release at the **same bucket with a different prefix**, or at a new bucket.
  </Step>

  <Step title="Re-ingest your data">
    Do **not** try to adopt the old release's volume set. It holds metadata whose `<macros><shard>` identity and Keeper paths belong to the old topology, and it cannot be moved between releases.
  </Step>

  <Step title="Cut over, then uninstall the old release">
    Point your applications at the new endpoint, then uninstall the old release **against the GVC you originally installed it into** — not the GVC it created. That is where Helm tracks the release, and uninstalling from there takes the created GVC with it.
  </Step>
</Steps>

Renamed in 3.0.0: `gvc.locations` is now the top-level `locations`, and `server.internal_access` / `keeper.internal_access` are now `server.internalAccess` / `keeper.internalAccess`.

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
# ─── Locations ────────────────────────────────────────────────────────────────
# This chart deploys into the GVC you install into — it does NOT create one.
# Every location listed here MUST already exist in that GVC, or the nodes refuse
# to initialise with a named error (see Prerequisites in the README).
# Extra locations in the GVC are fine: nothing ClickHouse-related runs in them.
#
# Deployment mode is derived from this list:
#   1 location,  replicas: 1   → single-node (no Keeper)
#   1 location,  replicas: >1  → single-shard cluster (Keeper required)
#   3 or more locations        → multi-shard cluster, one shard per location
#   2 locations                → NOT supported
locations:
  - name: aws-us-east-1
    replicas: 1

# ─── Object Storage ───────────────────────────────────────────────────────────
provider: aws # Options: aws, gcp, azure, or hetzner

aws: # If enabled, all fields below are required - See README for guidance
  bucket: my-clickhouse-bucket # Name of your S3 bucket
  region: us-east-1 # Region of your S3 bucket
  cloudAccountName: my-clickhouse-cloudaccount # Name of your Cloud Account
  policyName: my-clickhouse-s3-policy # Name of your pre-created policy to allow access to the S3 bucket

gcp: # If enabled, all fields below are required - See README for guidance
  bucket: my-clickhouse-gcs-bucket # Name of your GCS bucket
  # REQUIRED PREREQUISITE SECRET — a `dictionary` secret holding exactly
  # `accessKeyId` and `secretAccessKey` (the GCS interoperability HMAC pair).
  # They reach ClickHouse as AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY rather
  # than being written into the disk XML, because a cpln:// reference inside a
  # mounted config file is never resolved.
  credentialsSecretName: my-clickhouse-gcs-credentials

azure: # If enabled, all fields below are required - See README for guidance
  storageAccount: myclickhousestorage # Name of your Azure Storage Account
  container: clickhouse-data # Name of your Blob Storage container
  # REQUIRED PREREQUISITE SECRET — a `dictionary` secret holding exactly
  # `accountKey` for your Azure Storage Account.
  credentialsSecretName: my-clickhouse-azure-credentials

hetzner: # If enabled, all fields below are required - See README for guidance
  bucket: my-clickhouse-hetzner-bucket # Name of your Hetzner Object Storage bucket
  region: nbg1 # Region of your bucket. Options: nbg1, hel1, fsn1
  # REQUIRED PREREQUISITE SECRET — a `dictionary` secret holding exactly
  # `accessKeyId` and `secretAccessKey` for your Hetzner Object Storage.
  credentialsSecretName: my-clickhouse-hetzner-credentials

# ─── Cluster ──────────────────────────────────────────────────────────────────
# Used in cluster modes only. Must be a bare identifier: letters, digits and
# underscores, not starting with a digit — it becomes an XML element name and is
# used unquoted in `ON CLUSTER` DDL.
clusterName: my_cluster

database: # Automatically create a database on initialization using the default user
  # REQUIRED PREREQUISITE SECRET — CREATE IT BEFORE YOU INSTALL.
  # A `dictionary` secret holding exactly two keys: `password` and `database`.
  # ClickHouse has no separate username here — it uses the built-in `default`
  # user, so there is no `username` key. If the secret does not exist at install
  # time the deployment WEDGES silently; see Prerequisites in the README.
  credentialsSecretName: my-clickhouse-credentials

# ─── Storage ──────────────────────────────────────────────────────────────────
volumeset:
  server:
    capacity: 10 # initial capacity in GiB (minimum is 10)
  keeper:
    capacity: 10 # initial capacity in GiB (minimum is 10) - cluster modes only

# ─── Server ───────────────────────────────────────────────────────────────────
server:
  image: clickhouse/clickhouse-server:25.10
  resources:
    cpu: 2
    memory: 2Gi
  internalAccess:
    type: same-gvc # options: same-gvc, same-org, workload-list, none
    workloads: [] # required when type is workload-list; list only your clients -- this release's
    # own server and keeper workloads are added automatically, e.g. //gvc/GVC_NAME/workload/WORKLOAD_NAME

# ─── Keeper (cluster modes only) ──────────────────────────────────────────────
keeper: # cluster modes only
  image: clickhouse/clickhouse-keeper:25.10
  resources:
    cpu: 2
    memory: 2Gi
  internalAccess:
    type: same-gvc # options: same-gvc, same-org, workload-list, none
    workloads: [] # required when type is workload-list; list only your clients -- this release's
    # own server and keeper workloads are added automatically, e.g. //gvc/GVC_NAME/workload/WORKLOAD_NAME
```

### Locations

Each entry in `locations` pairs a location with a replica count, and the list length selects the deployment mode — see [Deployment Modes](#deployment-modes). Every location listed must already exist in the GVC you install into; extra GVC locations run nothing. Two locations, a duplicate location, an empty list and `replicas: 0` are all refused at render.

<Note>
  GVC locations you did not list show as red in the console, with `This workload location is deactivated because maxScale is set to 0.` That is the mechanism that keeps a shared GVC safe — it is what a healthy install looks like, not a fault.
</Note>

### Provider and Object Storage

Set `provider` to `aws`, `gcp`, `azure`, or `hetzner`, then fill in the corresponding section. Only the active provider's fields are used. See [Object storage](#object-storage) for the per-provider setup steps.

**AWS S3**

| Field                  | Description                                            |
| ---------------------- | ------------------------------------------------------ |
| `aws.bucket`           | Name of the S3 bucket                                  |
| `aws.region`           | AWS region where the bucket resides                    |
| `aws.cloudAccountName` | Name of the Control Plane Cloud Account with S3 access |
| `aws.policyName`       | Name of the IAM policy granting access to the bucket   |

**GCS**

| Field                       | Description                                                                                    |
| --------------------------- | ---------------------------------------------------------------------------------------------- |
| `gcp.bucket`                | Name of the GCS bucket                                                                         |
| `gcp.credentialsSecretName` | Dictionary secret holding `accessKeyId` and `secretAccessKey` — the interoperability HMAC pair |

**Azure Blob Storage**

| Field                         | Description                            |
| ----------------------------- | -------------------------------------- |
| `azure.storageAccount`        | Name of the Azure Storage Account      |
| `azure.container`             | Name of the Blob Storage container     |
| `azure.credentialsSecretName` | Dictionary secret holding `accountKey` |

**Hetzner Object Storage**

| Field                           | Description                                                   |
| ------------------------------- | ------------------------------------------------------------- |
| `hetzner.bucket`                | Name of the Hetzner Object Storage bucket                     |
| `hetzner.region`                | Bucket region — `nbg1`, `hel1`, or `fsn1`                     |
| `hetzner.credentialsSecretName` | Dictionary secret holding `accessKeyId` and `secretAccessKey` |

<Note>
  **Switch providers with a fresh install, not an upgrade.** An identity's cloud binding is never removed once set — the API merges rather than replaces — so a release switched from one provider to another keeps the old provider's binding attached even though the chart no longer renders it.
</Note>

### Cluster and Database

* `clusterName` — The name used for distributed DDL queries, in cluster modes only. It must be a bare identifier — letters, digits and underscores, not starting with a digit — because it becomes an XML element name and is used unquoted in `ON CLUSTER` statements. Anything else is refused at render.
* `database.credentialsSecretName` — Name of the dictionary secret holding `password` and `database`. ClickHouse creates that database on first initialization, and the password is the one every client connection uses.

The secret must exist before installing — see [Database credentials](#database-credentials). The workloads read it through `cpln://secret/...` references, so the password appears in neither the Helm release nor the stored workload spec.

<Warning>
  **Renaming `clusterName` on an existing install orphans its `Distributed` tables.** They keep pointing at the old cluster name and fail with `Code: 701 ... Requested cluster 'my_cluster' not found (CLUSTER_DOESNT_EXIST)`. Recreate them after a rename.
</Warning>

<Note>
  Credentials are applied only on first initialization, when the data directory is empty. Changing the secret afterwards does **not** change the running cluster — it only changes what the workload presents when it authenticates, which will then fail. To rotate on an existing cluster, run `ALTER USER default IDENTIFIED WITH sha256_password BY '...'` first, then update the secret, then force a redeployment. Updating a `cpln://` secret does not restart the workload by itself.
</Note>

### Images

* `server.image` — ClickHouse Server container image.
* `keeper.image` — ClickHouse Keeper container image. Only used in cluster modes.

### Resources and Storage

* `server.resources` / `keeper.resources` — CPU and memory allocated to each workload.
* `volumeset.server.capacity` — Persistent volume size in GiB for server metadata and cache (minimum 10).
* `volumeset.keeper.capacity` — Persistent volume size in GiB for Keeper state (minimum 10). Only used in cluster modes.

### Internal Access

Both `server.internalAccess` and `keeper.internalAccess` control which workloads can reach each component. Neither workload is exposed publicly, and the template has no public access option.

| Type            | Description                                                |
| --------------- | ---------------------------------------------------------- |
| `same-gvc`      | Allow access from all workloads in the same GVC            |
| `same-org`      | Allow access from all workloads in the same organization   |
| `workload-list` | Allow access only from the workloads listed in `workloads` |
| `none`          | Allow no internal access at all                            |

<Note>
  **List only your clients.** The server and Keeper reach each other over the same internal firewall, so the chart adds this release's own workloads to every list it renders. Without that, a `workload-list` naming only your applications takes down Keeper's Raft quorum and every cross-shard query while all status surfaces stay green.
</Note>

An access-knob change takes up to about five minutes to propagate — measured arms on this template settled between 55 and 123 seconds. Re-test before concluding it did not apply.

## Connecting to ClickHouse

| What                                           | Where                                                                            |
| ---------------------------------------------- | -------------------------------------------------------------------------------- |
| Public endpoint                                | **None.** This template exposes no public access                                 |
| Native protocol (clients, `clickhouse-client`) | `RELEASE_NAME-clickhouse-server.GVC_NAME.cpln.local:9000`                        |
| HTTP interface                                 | `RELEASE_NAME-clickhouse-server.GVC_NAME.cpln.local:8123`                        |
| A specific replica                             | `replica-INDEX.RELEASE_NAME-clickhouse-server.LOCATION.GVC_NAME.cpln.local:9000` |
| Keeper (cluster modes)                         | `replica-0.RELEASE_NAME-clickhouse-keeper.LOCATION.GVC_NAME.cpln.local:9181`     |
| Username                                       | `default` — there is no other user                                               |
| Password / database name                       | the `password` and `database` entries of your credentials secret                 |

From another workload in the same GVC:

```bash theme={null}
clickhouse-client --host RELEASE_NAME-clickhouse-server.GVC_NAME.cpln.local \
  --port 9000 --user default --password 'YOUR-PASSWORD'
```

Always use the fully qualified `.GVC_NAME.cpln.local` form. The bare workload name is not reliable on this platform — whether it resolves depends on the workload type.

### Tables in Cluster Modes

Use `ReplicatedMergeTree` plus a `Distributed` table. A plain `MergeTree` in a multi-shard cluster is single-copy and is not covered by the cluster's availability story.

```sql theme={null}
CREATE TABLE events_local ON CLUSTER my_cluster (id UInt64, ts DateTime)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
ORDER BY id;

CREATE TABLE events ON CLUSTER my_cluster AS events_local
ENGINE = Distributed(my_cluster, currentDatabase(), events_local, rand());
```

`{shard}` and `{replica}` come from each node's `<macros>`, which the chart derives from the location's position in `locations`.

## Important Notes

* **Never upgrade a 2.x release onto 3.0.0 in place** — it deletes the GVC the 2.x chart created and everything in it. Install a new release: [Migrating from 2.x](#migrating-from-2-x).
* **The GVC must contain every location you list**, and may contain more. A missing location is not caught at install: the container exits with `FATAL: locations declared in values are not in GVC ...`. An already-initialized node logs a `WARNING` instead and keeps serving.
* **Two locations is not supported.** Use one (single-node or single-shard) or three or more.
* **Object storage is required in every mode**, including single-node. There is no local-only shape.
* **Keeper is the availability floor.** Three members tolerate one loss; the single-shard shape has one member and tolerates none. If a majority of Keeper locations are missing from the GVC, the containers exit with a named error rather than waiting for an election that can never complete.
* **A `helm upgrade` restarts every replica in every location at once.** Nothing serializes a rolling restart on a stateful workload, so treat an upgrade as a planned query interruption: a rolling upgrade of a 3-shard cluster was measured at **about 83 seconds of total unavailability**, and a Keeper settings change at roughly **60 seconds** of coordination outage. On a single-node install, the first no-op upgrade after an install also restarts the one replica.
* **With one replica per shard, losing a shard fails every distributed query**, not just the rows on that shard — measured at about 60 seconds to surface, and about 112 seconds to full recovery. Add replicas if partial results are not acceptable.
* **Renaming `clusterName` orphans existing `Distributed` tables** (`Code: 701`). Recreate them after a rename.
* **Keep locations and the bucket in the same provider and region family.** Cross-region traffic to object storage is billed on every query that misses the local cache.
* **Release names must be unique per org** — secrets are org-wide, so two releases with the same name collide even in different GVCs.

## External References

<CardGroup cols={2}>
  <Card title="ClickHouse Documentation" icon="book" href="https://clickhouse.com/docs/">
    Official ClickHouse documentation
  </Card>

  <Card title="ClickHouse Keeper" icon="sitemap" href="https://clickhouse.com/docs/guides/sre/keeper/clickhouse-keeper">
    Raft coordination for replicated tables
  </Card>

  <Card title="Data Replication" icon="copy" href="https://clickhouse.com/docs/engines/table-engines/mergetree-family/replication">
    The ReplicatedMergeTree engine family
  </Card>

  <Card title="Distributed Table Engine" icon="table" href="https://clickhouse.com/docs/engines/table-engines/special/distributed">
    Fan a query out across shards
  </Card>

  <Card title="ClickHouse with S3" icon="aws" href="https://clickhouse.com/docs/integrations/s3">
    Integrating ClickHouse with AWS S3 and S3-compatible providers
  </Card>

  <Card title="ClickHouse with GCS" icon="google" href="https://clickhouse.com/docs/integrations/gcs">
    Integrating ClickHouse with Google Cloud Storage
  </Card>

  <Card title="ClickHouse with Azure Blob Storage" icon="microsoft" href="https://clickhouse.com/docs/engines/table-engines/integrations/azureBlobStorage">
    Integrating ClickHouse with Azure Blob Storage
  </Card>

  <Card title="ClickHouse Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/clickhouse">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
