> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy ClickHouse on Bare Metal

This tutorial walks you through deploying ClickHouse Private on your own bare-metal or on-premises Kubernetes cluster. By the end, you will have a running ClickHouse cluster with local NVMe SSD caching, MinIO AIStor-backed object storage, and the ClickHouse operator managing the deployment.

This guide does not assume a managed Kubernetes service or a specific Kubernetes distribution. It states the requirements your cluster must meet, then walks through the ClickHouse-specific setup. Any conformant Kubernetes distribution that satisfies [Step 1](#step-1-kubernetes-cluster-requirements), such as RKE2, kubeadm, or k3s, should work, though host-level factors outside Kubernetes (for example restrictive SELinux policies) can still affect a given environment.

For detailed infrastructure specifications, see [reference/infrastructure-requirements.md](/docs/cloud/clickhouse-private/reference/infrastructure-requirements). For an overview of how the operator works, see [explanation/architecture.md](/docs/cloud/clickhouse-private/explanation/architecture).

***

## Prerequisites

Before you begin, ensure you have the following tools installed:

* **kubectl** -- compatible with your target Kubernetes version
* **Helm** v3.x
* **skopeo** -- for copying container images between registries

You will also need:

* A **Kubernetes cluster** that meets the requirements in [Step 1](#step-1-kubernetes-cluster-requirements)
* A **private, OCI-compatible container registry** reachable from the cluster, to hold the mirrored ClickHouse images and Helm charts
* Access to the ClickHouse Private ECR repository (`<<SOURCE_ECR_ACCOUNT_ID>>.dkr.ecr.us-east-1.amazonaws.com`). The source ECR account ID and pull credentials are provided by ClickHouse during onboarding.
* The version tags for your deployment (provided by ClickHouse):
  * `<<SERVER_TAG>>` -- ClickHouse server image tag
  * `<<KEEPER_TAG>>` -- ClickHouse keeper image tag
  * `<<OPERATOR_TAG>>` -- Operator image and Helm chart tag
  * `<<CR_HELM_TAG>>` -- ClickHouse Cluster Helm Chart tag

***

## Step 1: Kubernetes Cluster Requirements

ClickHouse Private runs on any conformant Kubernetes cluster that meets the requirements below. How you provision that cluster (bare-metal installer, kubeadm, RKE2, k3s, an existing on-premises platform, etc.) is up to you.

### Cluster

| Requirement        | Details                                                                                |
| ------------------ | -------------------------------------------------------------------------------------- |
| Kubernetes version | 1.33 or later.                                                                         |
| CNI                | Any CNI providing standard IPv4 pod networking (for example Cilium, Calico, or Canal). |
| Container runtime  | Any CRI-compatible runtime (for example containerd).                                   |
| DNS                | A working in-cluster DNS (for example CoreDNS).                                        |

### Failure domains (availability zones)

The operator spreads ClickHouse Keeper across **at least three failure domains** using the `topology.kubernetes.io/zone` node label (Keeper's pod topology spread requires a minimum of three domains with a max skew of one). Server replicas are likewise balanced across the zones you configure on the operator.

On bare metal you assign these zones yourself by labeling nodes. Map each zone to a real fault-isolation boundary where possible -- a separate rack, chassis, power feed, or room -- so that losing one domain never takes down a Keeper quorum.

* Use **three distinct** `topology.kubernetes.io/zone` values (for example `zone-a`, `zone-b`, `zone-c`).
* Place at least one Keeper node in each zone.
* Distribute Server nodes across the same three zones.

<Note>
  **Keeper runs as a quorum**

  ClickHouse Keeper requires a quorum to operate, so deploy at least one Keeper node in each of the three zones. Spreading Keeper across three failure domains keeps the quorum available even if a single zone is lost.
</Note>

The zone label values you choose here are the ones you pass to the operator in [Step 6](#step-6-install-the-operator) as `operator.availabilityZones`.

### Node roles

Dedicate nodes to ClickHouse Server and Keeper, and keep at least one additional node available for the operator and cluster add-ons. Label and taint the ClickHouse nodes so the operator schedules pods correctly and nothing else lands on them.

| Role               | Count                                   | Node label               | Taint                                            | `topology.kubernetes.io/zone` |
| ------------------ | --------------------------------------- | ------------------------ | ------------------------------------------------ | ----------------------------- |
| Keeper             | 3 (one per zone)                        | `clickhouseGroup=keeper` | `clickhouse.com/do-not-schedule=true:NoSchedule` | one distinct zone per node    |
| Server             | As many as needed (spread across zones) | `clickhouseGroup=server` | `clickhouse.com/do-not-schedule=true:NoSchedule` | spread across the three zones |
| Operator / add-ons | 1+                                      | (none required)          | (none)                                           | --                            |

Apply the labels and taints with `kubectl label` / `kubectl taint`, for example:

```bash theme={null}
# Keeper node in zone-a
kubectl label node <keeper-node-a> clickhouseGroup=keeper topology.kubernetes.io/zone=zone-a --overwrite
kubectl taint node <keeper-node-a> clickhouse.com/do-not-schedule=true:NoSchedule --overwrite

# Server node in zone-a
kubectl label node <server-node-a> clickhouseGroup=server topology.kubernetes.io/zone=zone-a --overwrite
kubectl taint node <server-node-a> clickhouse.com/do-not-schedule=true:NoSchedule --overwrite
```

Repeat for `zone-b` and `zone-c`.

<Note>
  **Sizing**

  Keeper nodes are light (a few cores and \~16 GiB RAM is typical). Server nodes should be sized for your workload and **must have local NVMe SSD** for the ClickHouse disk cache -- see [Step 3](#step-3-provision-local-storage). Recommended starting points are in [reference/infrastructure-requirements.md](/docs/cloud/clickhouse-private/reference/infrastructure-requirements).
</Note>

***

## Step 2: Mirror Container Images

Copy the ClickHouse images and Helm charts from the ClickHouse ECR into your private registry using skopeo. The `--all` flag preserves all architectures (amd64, arm64).

The commands below refer to your registry as `$REGISTRY_HOST` (for example `registry.internal:5000` or a project path on a hosted registry). Adjust the login step to match how your registry authenticates.

```bash theme={null}
SOURCE_ECR_ACCOUNT_ID=<<SOURCE_ECR_ACCOUNT_ID>>
SOURCE_ECR_REPO=$SOURCE_ECR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
REGISTRY_HOST=registry.internal:5000

# log into the source ClickHouse ECR (requires the AWS credentials provided by ClickHouse)
aws ecr get-login-password --region us-east-1 | skopeo login --username AWS --password-stdin $SOURCE_ECR_REPO

# log into your private registry (authentication method may differ for your registry)
skopeo login $REGISTRY_HOST

# copy each image, be sure to include the --all flag
skopeo copy --all docker://$SOURCE_ECR_REPO/clickhouse-server:<<SERVER_TAG>> docker://$REGISTRY_HOST/clickhouse-server:<<SERVER_TAG>>
skopeo copy --all docker://$SOURCE_ECR_REPO/clickhouse-keeper:<<KEEPER_TAG>> docker://$REGISTRY_HOST/clickhouse-keeper:<<KEEPER_TAG>>
skopeo copy --all docker://$SOURCE_ECR_REPO/clickhouse-operator:main-<<OPERATOR_TAG>> docker://$REGISTRY_HOST/clickhouse-operator:main-<<OPERATOR_TAG>>
skopeo copy --all docker://$SOURCE_ECR_REPO/helm/clickhouse-operator-helm:<<OPERATOR_TAG>> docker://$REGISTRY_HOST/helm/clickhouse-operator-helm:<<OPERATOR_TAG>>
skopeo copy --all docker://$SOURCE_ECR_REPO/helm/onprem-clickhouse-cluster:<<CR_HELM_TAG>> docker://$REGISTRY_HOST/helm/onprem-clickhouse-cluster:<<CR_HELM_TAG>>
```

The nodes pull these images directly from `$REGISTRY_HOST`, so the registry must be reachable from every ClickHouse node. If your registry requires authentication for pulls, create the operator namespace and its image pull secret now; the same secret is referenced by the operator install in [Step 6](#step-6-install-the-operator), and the equivalent is created in the cluster namespace in [Step 7](#step-7-deploy-a-clickhouse-cluster).

```bash theme={null}
kubectl create namespace clickhouse-operator-system

kubectl create secret docker-registry registry-credentials \
  --docker-server=$REGISTRY_HOST \
  --docker-username=<username> \
  --docker-password=<password> \
  -n clickhouse-operator-system
```

***

## Step 3: Provision Local Storage

Two kinds of local storage are needed, and they are provisioned differently:

* **Server filesystem cache** -- backed by local **NVMe SSD** on the Server nodes. This cache sits in front of object storage and is critical to query performance; see [ClickHouse Filesystem Cache for Shared Merge Tree](/docs/cloud/clickhouse-private/explanation/clickhouse-filesystem-cache). Server pods are stateless by default (data lives in object storage), so the NVMe cache is the only local disk a Server node needs.
* **Keeper data** -- a small persistent volume for Keeper's Raft logs and snapshots, served by a StorageClass. Keeper does **not** use NVMe.

<Note>
  **Durability of local storage**

  ClickHouse's source of truth lives in the S3-compatible object store, so the local disks provisioned here do not need to be highly durable. The filesystem cache repopulates from object storage after a loss, and a Keeper node restores its state from the rest of the quorum.
</Note>

### Prepare the NVMe filesystem cache (Server nodes only)

On each **Server** node, mount the NVMe SSD at a consistent path -- this guide uses `/nvme/disk`. If a node has more than one NVMe device, combine them into a single RAID-0 array for the best throughput. Format as `ext4` and mount with `noatime,nobarrier`, then make the mount persistent in `/etc/fstab`.

For example, with two NVMe devices:

```console theme={null}
# combine the NVMe devices into a RAID-0 array
sudo mdadm --create --verbose /dev/md0 \
  --level=0 \
  --chunk=512 \
  --raid-devices=2 \
  /dev/nvme1n1 /dev/nvme2n1   # devices from `nvme list`

# format and mount at /nvme/disk
sudo mkfs.ext4 /dev/md0
sudo mkdir -p /nvme/disk
# add to /etc/fstab using the device UUID from `blkid`, then mount:
#   UUID=<device UUID>  /nvme/disk  ext4  defaults,noatime,nobarrier  0 0
sudo mount -a
```

For the full rationale (RAID chunk size, filesystem options, sizing) see [Configure and operate the filesystem cache](/docs/cloud/clickhouse-private/how-to/configure-and-operate-the-filesystem-cache).

The ClickHouse operator mounts this NVMe into the Server pods itself -- it supports two volume types for the cache:

* **HostPath** -- maps the host's `/nvme/disk` directly into the pod. This is the default on AWS and the simplest fit for bare metal, since you have already mounted the NVMe at a host path.
* **EmptyDir** -- backed by the node's [local ephemeral storage](https://kubernetes.io/docs/concepts/storage/ephemeral-storage/); requires the kubelet's ephemeral storage to live on the NVMe.

This guide uses **HostPath** at `/nvme/disk`; you configure it on the cluster in [Step 7](#step-7-deploy-a-clickhouse-cluster) via `server.ssdCacheConfiguration`. For a production setup we strongly recommend using \*\*EmptyDir•• mode,
as Local Ephemeral Storage reduces the attack surface and long term is operationally simpler.

For the trade-offs between the two modes, see [EmptyDir vs HostPath](/docs/cloud/clickhouse-private/how-to/configure-and-operate-the-filesystem-cache#emptydir-vs-hostpath).

### Provide a StorageClass for Keeper

Keeper stores its data on a persistent volume provisioned by a StorageClass. Pick any StorageClass whose PVs include a **zone label** (`topology.kubernetes.io/zone`, or a label matching `topology.*zone` / `topology.*node`) in their node affinity. The ClickHouse operator uses that zone information from Keeper PVs to enforce Keeper's cross-zone topology spread; a StorageClass whose PVs only carry `kubernetes.io/hostname` affinity (as stock Rancher `local-path-provisioner` does) is not enough on its own.

Zone-aware options on bare metal include:

* A cloud CSI driver, if the cluster is running in a cloud (for example the AWS EBS CSI driver with a `gp3` StorageClass)
* A zone-aware storage layer such as [OpenEBS](https://openebs.io/), [Rook](https://rook.io/), or [Longhorn](https://longhorn.io/)

You reference this StorageClass by name for Keeper storage in [Step 7](#step-7-deploy-a-clickhouse-cluster).

***

## Step 4: Deploy Object Storage

ClickHouse Private stores all of its data in S3-compatible object storage. On bare metal you provide this yourself. The recommended on-premises option is **[MinIO AIStor](https://www.min.io/)**, MinIO's commercial object store; any other S3-compatible object store (on-prem or external) works as well, as long as it is reachable from the cluster.

### Deploy MinIO AIStor

Install AIStor following [MinIO's documentation](https://docs.min.io/). A few points specific to this deployment:

* **Provision AIStor for performance.** ClickHouse performance depends directly on the object store: if AIStor cannot sustain the load, neither can ClickHouse. Give it high network throughput and performant disks.
* Expose an **S3 API endpoint** reachable from the ClickHouse pods (in-cluster service DNS or an ingress/load balancer). If you terminate TLS on the endpoint, the CA must be trusted by the ClickHouse pods.
* Ensure it is configured for **strict read-after-write** consistency.
* Avoid object versioning as this can leave data on MinIO AIStor even if it is deleted in ClickHouse.
* Size it for your dataset plus growth; ClickHouse retains all primary data here.

If you already operate Minio AIStor, skip the install and use its endpoint.

### Create a bucket and credentials

ClickHouse authenticates to the object store with a **static access key / secret key** pair. Provision the following and record the values for [Step 7](#step-7-deploy-a-clickhouse-cluster):

| Value             | Notes                                                                                      |
| ----------------- | ------------------------------------------------------------------------------------------ |
| Endpoint          | The S3 API URL, for example `https://minio.<your-domain>`                                  |
| Bucket            | One bucket per ClickHouse cluster, or a shared bucket with a unique key prefix per cluster |
| Access key ID     | Grants read/write on the bucket                                                            |
| Secret access key | Paired with the access key ID                                                              |

<Warning>
  **Do not create object lifecycle / expiration rules on this bucket**

  ClickHouse manages its own data in object storage. Lifecycle rules (expiration, tiering, aborting incomplete multipart uploads) will delete objects that ClickHouse still depends on, causing **data loss** and **cluster outages**. To manage data retention, use ClickHouse TTL rules and partition operations instead. See [Manage data lifecycle](/docs/cloud/clickhouse-private/how-to/manage-data-lifecycle).
</Warning>

***

## Step 5: Install Kubernetes Prerequisites

### Install VolumeSnapshot CRDs

These CRDs are required by the ClickHouse operator. If your cluster does not already provide them, install them from the external-snapshotter project:

```bash theme={null}
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
```

In a disconnected environment, mirror these manifests alongside your other artifacts and apply them from your internal source.

***

## Step 6: Install the Operator

Log into your registry from Helm if it requires authentication, then install the operator. Set the availability zones to the `topology.kubernetes.io/zone` labels you assigned in [Step 1](#step-1-kubernetes-cluster-requirements).

```bash theme={null}
REGISTRY_HOST=registry.internal:5000

# operator Helm chart tag (eg <<OPERATOR_TAG>>, not main-<<OPERATOR_TAG>>)
OPERATOR_VERSION=<<OPERATOR_TAG>>

# operator image tag -- the build itself (note the main- prefix)
OPERATOR_IMAGE_TAG=main-<<OPERATOR_TAG>>

# the zones you labeled nodes with in Step 1
ZONES='["zone-a","zone-b","zone-c"]'

# authenticate Helm to your registry (method may differ for your registry)
helm registry login $REGISTRY_HOST

helm install clickhouse-operator \
   oci://$REGISTRY_HOST/helm/clickhouse-operator-helm \
   --version=$OPERATOR_VERSION \
   --create-namespace \
   -n clickhouse-operator-system \
   --set-json="image.repository=\"$REGISTRY_HOST/clickhouse-operator\"" \
   --set-json="image.tag=\"$OPERATOR_IMAGE_TAG\"" \
   --set-json='cilium.enabled=false' \
   --set-json='idleScalerEnabled=false' \
   --set-json='webhooks.enabled=false' \
   --set-json="operator.availabilityZones=$ZONES" \
   --set-json='imagePullSecrets=[{"name":"registry-credentials"}]'
```

The final `imagePullSecrets` flag references the pull secret created in [Step 2](#step-2-mirror-container-images). Omit it if your registry allows unauthenticated pulls.

***

## Step 7: Deploy a ClickHouse Cluster

### Naming Your Cluster

Each ClickHouse cluster needs a **unique name** within the Kubernetes cluster. Use the convention `$DESCRIPTOR-$LETTERS-$ORDINAL`:

* `$DESCRIPTOR` -- descriptive name using letters only
* `$LETTERS` -- reserved, use `xx` for simplicity
* `$ORDINAL` -- incrementing ordinal starting with `01`
* Example: `default-xx-01`

### Create the Namespace and Secrets

Create the cluster namespace and, inside it, the object storage credentials from [Step 4](#step-4-deploy-object-storage) and the registry pull secret from [Step 2](#step-2-mirror-container-images):

```bash theme={null}
CLUSTER_NAME=default-xx-01
kubectl create namespace ns-$CLUSTER_NAME

# object storage credentials, referenced by the server and keeper env vars below
kubectl create secret generic object-storage-credentials \
  -n ns-$CLUSTER_NAME \
  --from-literal=access-key-id=<access-key-id> \
  --from-literal=secret-access-key=<secret-access-key>

# registry pull secret (skip if your registry allows unauthenticated pulls)
kubectl create secret docker-registry registry-credentials \
  --docker-server=$REGISTRY_HOST \
  --docker-username=<username> \
  --docker-password=<password> \
  -n ns-$CLUSTER_NAME
```

Storing the credentials in a Secret keeps them out of your shell history and the Helm release values.

### Generate Password Hash and Deploy

<Info>
  **Guaranteed QoS (recommended)**

  ClickHouse workloads should run with matching `requests` and `limits` for
  both CPU and memory. The single `SERVER_CPU`/`SERVER_MEMORY`/`KEEPER_CPU`/
  `KEEPER_MEMORY` values below are applied to **both** `resources.requests`
  and `resources.limits` in the helm invocation that follows, which places
  the pods in the [Guaranteed](https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/)
  QoS class. If you need to run with a different QoS class, review
  [Pod QoS: Guaranteed (recommended)](/docs/cloud/clickhouse-private/reference/infrastructure-requirements#pod-qos-guaranteed-recommended)
  first for the trade-offs.
</Info>

```bash theme={null}
# this will be the `default` user's password
PASSWORD='My super secret p@$$w0rd'
if command -v sha256sum &> /dev/null; then
  HASHED_PASSWORD=$(echo -n "$PASSWORD" | sha256sum | awk '{printf $1}' | base64 | tr -d '\n')
else
  HASHED_PASSWORD=$(echo -n "$PASSWORD" | shasum -a 256 | awk '{printf $1}' | base64 | tr -d '\n')
fi

# update values below as needed
CLUSTER_NAME=default-xx-01
REGISTRY_HOST=registry.internal:5000

# object storage (from Step 4)
S3_ENDPOINT=https://minio.<your-domain>
S3_BUCKET_NAME=my-clickhouse-data

# s3 key prefix can use any UUID value, but must be unique for all clusters storing data in the bucket
S3_KEY_PREFIX=ch-s3-$(uuidgen | tr '[:upper:]' '[:lower:]')

# these should match the clickhouseGroup labels you applied in Step 1
SERVER_NG_GROUP=server
KEEPER_NG_GROUP=keeper

# these values should change depending on your hardware.
# be sure to take DaemonSet requirements into account when setting CPU and MEMORY values
SERVER_CPU=64
SERVER_MEMORY=128Gi
KEEPER_CPU=4
KEEPER_MEMORY=16Gi

# size the cache at 70-80% of the usable NVMe capacity mounted at /nvme/disk (Step 3)
CACHE_DISK_SIZE=1400Gi

# StorageClass for Keeper's persistent volume (from Step 3). Must produce PVs with a zone label in their node affinity.
KEEPER_STORAGE_CLASS=<your-zone-aware-storageclass>

CHART_VERSION=<<CR_HELM_TAG>>

# S3 credentials are read from the object-storage-credentials Secret, shared by
# the server and keeper pods so both authenticate to the object store.
S3_CREDS_ENV='[
  {"name":"AWS_ACCESS_KEY_ID","valueFrom":{"secretKeyRef":{"name":"object-storage-credentials","key":"access-key-id"}}},
  {"name":"AWS_SECRET_ACCESS_KEY","valueFrom":{"secretKeyRef":{"name":"object-storage-credentials","key":"secret-access-key"}}}
]'

helm install $CLUSTER_NAME \
    oci://$REGISTRY_HOST/helm/onprem-clickhouse-cluster \
    --version=$CHART_VERSION \
    -n ns-$CLUSTER_NAME \
    --set-json="account.hashedPassword=\"$HASHED_PASSWORD\"" \
    --set-json='isOnPremiseInstance=true' \
    --set-json='imagePullSecrets=[{"name":"registry-credentials"}]' \
    --set-json="server.image.repository=\"$REGISTRY_HOST/clickhouse-server\"" \
    --set-json="server.image.tag=\"<<SERVER_TAG>>\"" \
    --set-json='server.arm64=false' \
    --set-json="server.storage.s3.bucketName=\"$S3_BUCKET_NAME\"" \
    --set-json="server.storage.s3.endpoint=\"$S3_ENDPOINT\"" \
    --set-json="server.storage.s3.keyPrefix=\"$S3_KEY_PREFIX\"" \
    --set-json="server.ssdCacheConfiguration.cacheDiskSize=\"$CACHE_DISK_SIZE\"" \
    --set-json="server.podPolicy.nodeSelector.clickhouseGroup=\"$SERVER_NG_GROUP\"" \
    --set-json="server.podPolicy.resources.limits.cpu=\"$SERVER_CPU\"" \
    --set-json="server.podPolicy.resources.limits.memory=\"$SERVER_MEMORY\"" \
    --set-json="server.podPolicy.resources.requests.cpu=\"$SERVER_CPU\"" \
    --set-json="server.podPolicy.resources.requests.memory=\"$SERVER_MEMORY\"" \
    --set-json="server.additionalEnvVars=$S3_CREDS_ENV" \
    --set-json='server.tolerations=[{"effect":"NoSchedule","key":"clickhouse.com/do-not-schedule","operator":"Exists"}]' \
    --set-json="keeper.image.repository=\"$REGISTRY_HOST/clickhouse-keeper\"" \
    --set-json="keeper.image.tag=\"<<KEEPER_TAG>>\"" \
    --set-json='keeper.arm64=false' \
    --set-json="keeper.storage.storageClassName=\"$KEEPER_STORAGE_CLASS\"" \
    --set-json="keeper.podPolicy.nodeSelector.clickhouseGroup=\"$KEEPER_NG_GROUP\"" \
    --set-json="keeper.podPolicy.resources.limits.cpu=\"$KEEPER_CPU\"" \
    --set-json="keeper.podPolicy.resources.limits.memory=\"$KEEPER_MEMORY\"" \
    --set-json="keeper.podPolicy.resources.requests.cpu=\"$KEEPER_CPU\"" \
    --set-json="keeper.podPolicy.resources.requests.memory=\"$KEEPER_MEMORY\"" \
    --set-json="keeper.additionalEnvVars=$S3_CREDS_ENV" \
    --set-json='keeper.tolerations=[{"effect":"NoSchedule","key":"clickhouse.com/do-not-schedule","operator":"Exists"}]'
```

<Tip>
  **Chart values reference**

  This install sets only the values a bare-metal deployment needs. For the full list of configurable values and their defaults, review the README of the `onprem-clickhouse-cluster` Helm chart.
</Tip>

Monitor the rollout (the operator creates server pods after keepers are healthy):

```bash theme={null}
kubectl get pods -n ns-$CLUSTER_NAME -w
```

***

## Step 8: Verify Installation

### Port-forward the ClickHouse Service

```bash theme={null}
kubectl port-forward svc/c-default-xx-01-server-any 9000:9000 -n ns-default-xx-01
```

This forwards port `9000` to your local machine.

### Connect and Run a Query

```bash theme={null}
clickhouse client --host localhost --port 9000 --password "$PASSWORD"
```

Run a simple query:

```sql theme={null}
SELECT 1;
```

Expected output:

```
   ┌─1─┐
1. │ 1 │
   └───┘
1 row in set. Elapsed: 0.001 sec.
```

### Confirm data reaches object storage

`SELECT 1` only proves the server is running. Because the object store is something you provide on bare metal, verify that ClickHouse can actually write to and read from it: create a table, insert data, then drop the local filesystem cache so the next read must come from object storage.

```sql theme={null}
CREATE TABLE default.connectivity_check (id UInt64, v String)
ENGINE = SharedMergeTree
ORDER BY id;

INSERT INTO default.connectivity_check
SELECT number, toString(number) FROM numbers(1000000);

-- Force the next read to come from object storage, not the local NVMe cache.
SYSTEM DROP FILESYSTEM CACHE;
SELECT count(), min(id), max(id) FROM default.connectivity_check;
```

A correct count after dropping the cache confirms the full write-then-read path through object storage. You can also list the objects under your bucket and `keyPrefix` with your object store's client or console to see the parts ClickHouse wrote.

***

## Next Steps

* **FIPS / government compliance:** See [tutorials/deploy-government.md](/docs/cloud/clickhouse-private/tutorials/deploy-government) to apply FIPS 140-3 certificates and TLS configuration on top of this infrastructure.
* **Compute-Compute separation:** See [how-to/configure-compute-compute-separation.md](/docs/cloud/clickhouse-private/how-to/configure-compute-compute-separation) to set up multiple compute groups with separate endpoints sharing a single dataset.
* **Management API:** See [tutorials/install-api.md](/docs/cloud/clickhouse-private/tutorials/install-api) to install the optional Private API for backups and scaling operations.
* **Monitoring and alerting:** See [how-to/configure-alerting.md](/docs/cloud/clickhouse-private/how-to/configure-alerting) to set up alerting for your deployment.
* **Troubleshooting:** See [troubleshooting.md](/docs/cloud/clickhouse-private/troubleshooting) for common issues and solutions.
