> ## 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 Azure AKS

This tutorial walks you through deploying ClickHouse Private on Microsoft Azure using Azure Kubernetes Service (AKS), step by step. By the end, you will have a running ClickHouse cluster with Azure Blob Storage-backed storage, local NVMe SSD caching, and the ClickHouse operator managing the deployment.

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:

* **Azure CLI** (`az`) -- installed and authenticated to your subscription
* **kubectl** -- compatible with your target AKS version
* **Helm** v3.x
* **skopeo** -- for copying container images between registries
* **AWS CLI** (`aws`) -- with read access to the ClickHouse private ECR (`<<SOURCE_ECR_ACCOUNT_ID>>.dkr.ecr.us-east-1.amazonaws.com`; access details provided by ClickHouse during onboarding), used only for the image copy step

You will also need:

* An Azure subscription with permissions to create AKS clusters, Azure Container Registry, Storage Accounts, Virtual Networks, and Managed Identities
* A **resource group** for all ClickHouse resources (or reuse an existing one)
* 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>>` -- Cluster Helm chart tag

***

## Step 1: Create the Azure Container Registry

Create an Azure Container Registry (ACR) to hold the ClickHouse images and Helm charts, then log in:

```bash theme={null}
AZURE_RESOURCE_GROUP=rg-clickhouse
AZURE_LOCATION=eastus
ACR_NAME=clickhouseregistry   # must be globally unique, alphanumeric only

az group create --name $AZURE_RESOURCE_GROUP --location $AZURE_LOCATION

az acr create \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $ACR_NAME \
  --sku Premium \
  --location $AZURE_LOCATION

az acr login --name $ACR_NAME
```

The rest of this guide refers to the registry host as `$ACR_HOST`:

```bash theme={null}
ACR_HOST=$ACR_NAME.azurecr.io
```

***

## Step 2: Copy Container Images

Use skopeo to copy images from the ClickHouse ECR into your ACR. The `--all` flag preserves all architectures (amd64, arm64).

```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

# log into the source ClickHouse ECR (requires 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 ACR
az acr login --name $ACR_NAME --expose-token --output tsv --query accessToken | \
  skopeo login --username 00000000-0000-0000-0000-000000000000 --password-stdin $ACR_HOST

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

Verify the copy:

```bash theme={null}
az acr repository list --name $ACR_NAME --output table
```

***

## Step 3: Create the Virtual Network

Create a VNet with a subnet for AKS nodes.

| Setting            | Value            |
| ------------------ | ---------------- |
| VNet address space | `10.20.0.0/16`   |
| AKS node subnet    | `10.20.0.0/22`   |
| Pod CIDR (overlay) | `192.168.0.0/16` |
| Service CIDR       | `10.0.0.0/16`    |
| DNS service IP     | `10.0.0.10`      |

```bash theme={null}
VNET_NAME=vnet-clickhouse
NODE_SUBNET_NAME=snet-aks-nodes

az network vnet create \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $VNET_NAME \
  --address-prefixes 10.20.0.0/16 \
  --location $AZURE_LOCATION

az network vnet subnet create \
  --resource-group $AZURE_RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $NODE_SUBNET_NAME \
  --address-prefixes 10.20.0.0/22

NODE_SUBNET_ID=$(az network vnet subnet show \
  --resource-group $AZURE_RESOURCE_GROUP \
  --vnet-name $VNET_NAME \
  --name $NODE_SUBNET_NAME \
  --query id -o tsv)
```

See [reference/infrastructure-requirements.md](/docs/cloud/clickhouse-private/reference/infrastructure-requirements) for detailed networking requirements.

***

## Step 4: Create the AKS Cluster

Create the AKS cluster with **OIDC issuer** and **Workload Identity** enabled (required for Step 6). Associate it with the VNet subnet from Step 3. The initial system node pool (`Standard_D4s_v3`) runs cluster-internal workloads and the ClickHouse operator.

```bash theme={null}
AKS_CLUSTER_NAME=aks-clickhouse

az aks create \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $AKS_CLUSTER_NAME \
  --location $AZURE_LOCATION \
  --vnet-subnet-id $NODE_SUBNET_ID \
  --network-plugin azure \
  --network-plugin-mode overlay \
  --pod-cidr 192.168.0.0/16 \
  --service-cidr 10.0.0.0/16 \
  --dns-service-ip 10.0.0.10 \
  --node-count 2 \
  --node-vm-size Standard_D4s_v3 \
  --os-sku AzureLinux \
  --enable-oidc-issuer \
  --enable-workload-identity \
  --attach-acr $ACR_NAME \
  --generate-ssh-keys

az aks get-credentials \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $AKS_CLUSTER_NAME

kubectl get nodes
```

Capture the OIDC issuer URL (needed for Workload Identity in Step 6):

```bash theme={null}
OIDC_ISSUER=$(az aks show \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $AKS_CLUSTER_NAME \
  --query oidcIssuerProfile.issuerUrl -o tsv)
```

***

## Step 5: Create Node Pools

Add two dedicated node pools for ClickHouse.

<Note>
  **Arm deployments**

  This guide uses x86 machine types (`server.arm64=false` / `keeper.arm64=false` in Step 9). Azure also offers Arm-based VMs (`Standard_D*ps_v5` series). To run on Arm, create the node pools with an Arm machine type, use distinct labels such as `clickhouseGroup: server-arm64` / `keeper-arm64`, point the Step 9 `nodeSelector` values at them, and set `server.arm64=true` / `keeper.arm64=true`.
</Note>

### Keeper Node Pool

| Setting           | Value                                            |
| ----------------- | ------------------------------------------------ |
| VM size           | `Standard_D8ds_v5`                               |
| OS disk           | 32 GB Premium SSD                                |
| Min/desired nodes | 3 per ClickHouse cluster                         |
| Kubernetes labels | `clickhouseGroup=keeper`                         |
| Kubernetes taints | `clickhouse.com/do-not-schedule=true:NoSchedule` |

```bash theme={null}
az aks nodepool add \
  --resource-group $AZURE_RESOURCE_GROUP \
  --cluster-name $AKS_CLUSTER_NAME \
  --name keeper \
  --node-count 3 \
  --node-vm-size Standard_D8ds_v5 \
  --os-sku AzureLinux \
  --node-osdisk-size 32 \
  --node-osdisk-type Managed \
  --zones 1 2 3 \
  --vnet-subnet-id $NODE_SUBNET_ID \
  --node-taints clickhouse.com/do-not-schedule=true:NoSchedule \
  --labels clickhouseGroup=keeper \
  --no-wait
```

### Server Node Pool

The server pool uses the `Standard_D*ds_v5` family. Setting `--node-osdisk-type Ephemeral` places the OS on the small local disk. Azure automatically mounts the larger local SSD at `/mnt`. The ClickHouse cache uses this disk via a `hostPath` volume pointing at `/mnt`.

| Setting           | Value                                                  |
| ----------------- | ------------------------------------------------------ |
| VM size           | `Standard_D16ds_v5` (dev) / `Standard_D48ds_v5` (prod) |
| OS disk           | Ephemeral (no managed disk cost)                       |
| Min/desired nodes | Equal to desired ClickHouse replicas                   |
| Kubernetes labels | `clickhouseGroup=server`                               |
| Kubernetes taints | `clickhouse.com/do-not-schedule=true:NoSchedule`       |

```bash theme={null}
az aks nodepool add \
  --resource-group $AZURE_RESOURCE_GROUP \
  --cluster-name $AKS_CLUSTER_NAME \
  --name server \
  --node-count 3 \
  --node-vm-size Standard_D16ds_v5 \
  --os-sku AzureLinux \
  --node-osdisk-size 50 \
  --node-osdisk-type Ephemeral \
  --zones 1 2 3 \
  --vnet-subnet-id $NODE_SUBNET_ID \
  --node-taints clickhouse.com/do-not-schedule=true:NoSchedule \
  --labels clickhouseGroup=server \
  --no-wait
```

In Step 9, set `server.ssdCacheConfiguration.hostPathBaseDirectory="/mnt"` (this also disables `isOnEmptyDir` automatically).

***

## Step 6: Create the Storage Account and Managed Identity

### Storage Account

Create an Azure Storage Account and a Blob container for ClickHouse data. Place it in the same region as the AKS cluster.

<Warning>
  **Do not create Blob Lifecycle Management rules on this container**

  ClickHouse manages its own data in Blob Storage. Lifecycle Management policies (deletion, tiering, abort incomplete uploads with short timeouts) will delete or transition 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>

```bash theme={null}
STORAGE_ACCOUNT_NAME=chstorage$RANDOM   # must be globally unique, 3-24 lowercase alphanumeric
BLOB_CONTAINER_NAME=clickhouse-data

az storage account create \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $STORAGE_ACCOUNT_NAME \
  --location $AZURE_LOCATION \
  --sku Standard_LRS \
  --kind StorageV2 \
  --allow-blob-public-access false

az storage container create \
  --account-name $STORAGE_ACCOUNT_NAME \
  --name $BLOB_CONTAINER_NAME \
  --auth-mode login
```

### Managed Identity and Workload Identity

```bash theme={null}
CLUSTER_NAME=default-xx-01
NAMESPACE=ns-$CLUSTER_NAME
UAMI_NAME=clickhouse-identity

# create the managed identity
az identity create \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $UAMI_NAME \
  --location $AZURE_LOCATION

UAMI_CLIENT_ID=$(az identity show \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $UAMI_NAME \
  --query clientId -o tsv)

UAMI_PRINCIPAL_ID=$(az identity show \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $UAMI_NAME \
  --query principalId -o tsv)

STORAGE_ACCOUNT_ID=$(az storage account show \
  --resource-group $AZURE_RESOURCE_GROUP \
  --name $STORAGE_ACCOUNT_NAME \
  --query id -o tsv)

# grant Storage Blob Data Contributor on the storage account
az role assignment create \
  --assignee-object-id $UAMI_PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal \
  --role "Storage Blob Data Contributor" \
  --scope $STORAGE_ACCOUNT_ID

# create the federated credential binding UAMI to the chart-created KSA
az identity federated-credential create \
  --resource-group $AZURE_RESOURCE_GROUP \
  --identity-name $UAMI_NAME \
  --name clickhouse-federated \
  --issuer $OIDC_ISSUER \
  --subject "system:serviceaccount:$NAMESPACE:ch-$CLUSTER_NAME-sa" \
  --audience api://AzureADTokenExchange
```

**Workload Identity alignment:**

| Component                          | Value                                                                            |
| ---------------------------------- | -------------------------------------------------------------------------------- |
| User-Assigned Managed Identity     | `clickhouse-identity` in `$AZURE_RESOURCE_GROUP`                                 |
| K8s ServiceAccount (chart-created) | `ch-$CLUSTER_NAME-sa` in namespace `ns-$CLUSTER_NAME`                            |
| KSA annotation                     | `azure.workload.identity/client-id=$UAMI_CLIENT_ID` (set by the chart in Step 9) |
| Federated subject                  | `system:serviceaccount:$NAMESPACE:ch-$CLUSTER_NAME-sa`                           |

***

## Step 7: Install Kubernetes Prerequisites

### Install VolumeSnapshot CRDs

These CRDs are required by the ClickHouse operator.

```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
```

### StorageClass

No manual StorageClass setup is required. The Helm chart creates the `premium-ssd-v2` StorageClass automatically when you set `storageClass.create=true` in Step 9. This StorageClass uses the pre-installed Azure Disk CSI driver (`disk.csi.azure.com`) with `PremiumV2_LRS` disks (`skuName: PremiumV2_LRS`, `fstype: ext4`, `cachingMode: None`).

***

## Step 8: Install the Operator

Log into ACR from Helm, then install the operator. Set the availability zones to match your cluster's zones.

```bash theme={null}
ACR_HOST=$ACR_NAME.azurecr.io

# 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>>

# Azure zone IDs include the region prefix -- NOT bare numbers like "1","2","3".
# Format: <region>-<zone>, e.g. westus3-1, eastus-1, westeurope-1
# To check: kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.labels.topology\.kubernetes\.io/zone}{"\n"}{end}' | sort -u
ZONES="[\"${AZURE_LOCATION}-1\",\"${AZURE_LOCATION}-2\",\"${AZURE_LOCATION}-3\"]"

az acr login --name $ACR_NAME
helm registry login $ACR_HOST \
  --username $(az acr credential show --name $ACR_NAME --query username -o tsv) \
  --password $(az acr credential show --name $ACR_NAME --query passwords[0].value -o tsv)

helm install clickhouse-operator \
   oci://$ACR_HOST/helm/clickhouse-operator-helm \
   --version=$OPERATOR_VERSION \
   --create-namespace \
   -n clickhouse-operator-system \
   --set-json="image.repository=\"$ACR_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"
```

***

## Step 9: Deploy a ClickHouse Cluster

### Naming Your Cluster

Each ClickHouse cluster needs a **unique name** within the AKS 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`

### Generate Password Hash and Deploy

```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
ACR_HOST=$ACR_NAME.azurecr.io

# Azure Blob Storage endpoint and container
AZURE_STORAGE_ENDPOINT="https://$STORAGE_ACCOUNT_NAME.blob.core.windows.net"

# these values should change depending on selected instance sizes
SERVER_CPU=16
SERVER_MEMORY=64Gi
KEEPER_CPU=4
KEEPER_MEMORY=16Gi

# bytesPerGiRAM is a scaling factor used to automatically calculate the disk cache size.
# Set cache size to ~75% of temp storage, accounting for OS (50GB) and AKS reserved space.
# At pod startup: CONFIG_DISK_CACHE_SIZE = bytesPerGiRAM * pod_memory_limit
#
# For Standard_D16ds_v5 (64 GiB RAM, 600 GiB temp):
# bytesPerGiRAM = 7Gi -> cache = 7 * 64 = 448 GiB (~75% of 600 GiB)
CACHE_BYTES_PER_GI_RAM=7Gi

# Verify UAMI_CLIENT_ID is set -- the SA annotation will be empty and workload identity will fail if this is blank
echo "UAMI_CLIENT_ID: $UAMI_CLIENT_ID"

CHART_VERSION=<<CR_HELM_TAG>>

helm install $CLUSTER_NAME \
    oci://$ACR_HOST/helm/onprem-clickhouse-cluster \
    --version=$CHART_VERSION \
    -n ns-$CLUSTER_NAME \
    --create-namespace \
    --set-json='baseConfiguration.cloud="azure"' \
    --set-json='storageClass.create=true' \
    --set-json="account.hashedPassword=\"$HASHED_PASSWORD\"" \
    --set-json="serviceAccount.annotations={\"azure.workload.identity/client-id\":\"$UAMI_CLIENT_ID\"}" \
    --set-json="server.image.repository=\"$ACR_HOST/clickhouse-server\"" \
    --set-json="server.image.tag=\"<<SERVER_TAG>>\"" \
    --set-json='server.arm64=false' \
    --set-json='server.storage.s3.type="azure_blob_storage"' \
    --set-json="server.storage.s3.endpoint=\"$AZURE_STORAGE_ENDPOINT\"" \
    --set-json="server.storage.s3.bucketName=\"$BLOB_CONTAINER_NAME\"" \
    --set-json="server.storage.s3.region=\"$AZURE_LOCATION\"" \
    --set-json="server.ssdCacheConfiguration.hostPathBaseDirectory=\"/mnt\"" \
    --set-json="server.ssdCacheConfiguration.bytesPerGiRAM=\"$CACHE_BYTES_PER_GI_RAM\"" \
    --set-json="server.podPolicy.nodeSelector.clickhouseGroup=\"server\"" \
    --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.tolerations=[{"effect":"NoSchedule","key":"clickhouse.com/do-not-schedule","operator":"Exists"}]' \
    --set-json="keeper.image.repository=\"$ACR_HOST/clickhouse-keeper\"" \
    --set-json="keeper.image.tag=\"<<KEEPER_TAG>>\"" \
    --set-json='keeper.arm64=false' \
    --set-json="keeper.podPolicy.nodeSelector.clickhouseGroup=\"keeper\"" \
    --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.tolerations=[{"effect":"NoSchedule","key":"clickhouse.com/do-not-schedule","operator":"Exists"}]'
```

<Tip>
  **Chart values reference**

  This install sets only the values an Azure 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 10: 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.
```

***

## 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.

***

## Appendix: AWS to Azure Component Mapping

| AWS Component                            | Azure Equivalent                                           | Notes                                                                                                  |
| ---------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| ECR                                      | Azure Container Registry (ACR)                             | `az acr login`; attach to AKS with `--attach-acr`                                                      |
| S3                                       | Azure Blob Storage                                         | Endpoint: `https://<account>.blob.core.windows.net`; set `server.storage.s3.type="azure_blob_storage"` |
| IAM IRSA                                 | Azure Workload Identity (UAMI + OIDC federated credential) | Annotate the KSA with `azure.workload.identity/client-id`; no static credentials                       |
| EBS CSI (`gp3-encrypted`)                | Azure Disk CSI (`premium-ssd-v2`)                          | Pre-installed in AKS; chart creates the StorageClass with `storageClass.create=true`                   |
| NVMe instance store (RAID via DaemonSet) | `Standard_D*ds_v5` local SSD                               | Azure auto-mounts at `/mnt`; use `hostPathBaseDirectory=/mnt` — no DaemonSet or RAID needed            |
| Availability Zones (`us-east-1a`)        | Azure Zones (`westus3-1`)                                  | Format is `<region>-<number>`, not a letter suffix                                                     |
| VPC                                      | Azure Virtual Network (VNet)                               | Use Azure CNI overlay (`--network-plugin-mode overlay`)                                                |
| EKS                                      | AKS                                                        | Enable `--enable-oidc-issuer --enable-workload-identity` at cluster creation                           |
