---
title: Create the Infrastructure for Running a Valkey Cluster on Azure Kubernetes Service (AKS)
description: In this article, you create the infrastructure for running a Valkey cluster on Azure Kubernetes Service (AKS).
ms.topic: how-to
ms.service: azure-kubernetes-service
ms.date: 09/15/2025
author: schaffererin
ms.author: schaffererin
zone_pivot_groups: azure-cli-or-terraform
ms.custom: 'stateful-workloads, aks-data-messaging'
# Customer intent: As a cloud architect, I want to set up the infrastructure for a Valkey cluster on a Kubernetes service, so that I can deploy and manage stateful applications efficiently in a scalable environment.
---

# Create the infrastructure for running a Valkey cluster on Azure Kubernetes Service (AKS)

This guide walks through the complete setup process for running a Valkey cluster on AKS. This process includes configuring environment variables, provisioning core Azure resources (such as a resource group, Azure Key Vault, and Azure Container Registry (ACR)), creating an AKS cluster with integrated workload identity and managing secrets. It also covers the creation of a dedicated node pool for Valkey workloads and how to import Valkey images into your private registry. If you prefer infrastructure-as-code, the guide includes an alternative deployment path using Terraform with Azure Verified Modules, ensuring best practices and production readiness.

:::zone pivot="terraform"

To deploy the infrastructure using Terraform, we use the [Azure Verified Module](https://azure.github.io/Azure-Verified-Modules/)[for AKS](https://github.com/Azure/terraform-azurerm-avm-res-containerservice-managedcluster.git).

> [!NOTE]
> If you're planning to run this deployment in production, we recommend looking at [AKS production pattern module for Azure Verified Modules](https://github.com/Azure/terraform-azurerm-avm-ptn-aks-production). This module comes coupled with best practice recommendations.

:::zone-end

## Prerequisites

- If you haven't already, review the [Overview for deploying a Valkey cluster on Azure Kubernetes Service (AKS)][valkey-solution-overview].
- An Azure subscription. If you don't have one, create a [free account][azure-free-account].
- Azure CLI version 2.61.0. To install or upgrade, see [Install Azure CLI][install-azure-cli].
- Helm version 3 or later. To install, see [Installing Helm][install-helm].
- `kubectl`, which the Azure Cloud Shell installs by default.
- Docker installed on your local machine. To install, see [Get Docker][install-docker].

:::zone pivot="azure-cli"

## Set environment variables

- Set the required environment variables for use throughout this guide:

    ```bash
    random=$(echo $RANDOM | tr '[0-9]' '[a-z]')
    export MY_RESOURCE_GROUP_NAME=myResourceGroup-rg-$(echo $random)
    export MY_LOCATION=centralus
    export MY_ACR_REGISTRY=mydns$(echo $random)
    export MY_KEYVAULT_NAME=vault-$(echo $random)-kv
    export MY_CLUSTER_NAME=cluster-aks
    ```

## Create a resource group

- Create a resource group using the [`az group create`][az-group-create] command.

    ```azurecli-interactive
    az group create --name $MY_RESOURCE_GROUP_NAME --location $MY_LOCATION --output table
    ```

    Example output:
    <!-- expected_similarity=0.8 -->
    ```output
    Location    Name
    ----------  ------------------
    eastus      myResourceGroup-rg
    ```

## Create an Azure Key Vault instance

- Create an Azure Key Vault instance using the [`az keyvault create`][az-keyvault-create]command. Azure Key Vault securely stores and accessing secrets such as API keys, passwords, certificates, or cryptographic keys.

    ```azurecli-interactive
    az keyvault create --name $MY_KEYVAULT_NAME --resource-group $MY_RESOURCE_GROUP_NAME --location $MY_LOCATION --enable-rbac-authorization false --output table
    ```

    Example output:
    <!-- expected_similarity=0.8 -->
    ```output
    Location    Name            ResourceGroup
    ----------  --------------  ------------------
    eastus      vault-bbbhe-kv  myResourceGroup-rg
    ```

## Create an Azure Container Registry

- Create an Azure Container Registry to store and manage your container images using the [`az acr create`][az-acr-create] command.

    ```azurecli-interactive
    az acr create \
      --name ${MY_ACR_REGISTRY} \
      --resource-group $MY_RESOURCE_GROUP_NAME \
      --sku Premium \
      --location $MY_LOCATION \
      --admin-enabled true \
      --output table
    export MY_ACR_REGISTRY_ID=$(az acr show --name $MY_ACR_REGISTRY --resource-group $MY_RESOURCE_GROUP_NAME --query id --output tsv)
    ```

    Example output:
    <!-- expected_similarity=0.8 -->
    ```output
    NAME                  RESOURCE GROUP      LOCATION    SKU      LOGIN SERVER                     CREATION DATE         ADMIN ENABLED
    --------------------  ------------------  ----------  -------  -------------------------------  --------------------  ---------------
    mydnsrandomnamebbbhe  myResourceGroup-rg  eastus      Premium  mydnsrandomnamebbbhe.azurecr.io  2024-06-11T09:36:43Z  True
    ```

## Create an AKS cluster

In this step, we create an AKS cluster. We enable the [Azure Key Vault Secrets provider add-on](./csi-secrets-store-driver.md) (`azure-keyvault-secrets-provider`), which allows the AKS cluster to access secrets stored in Azure Key Vault. We also enable [workload identity](./workload-identity-overview.md), which allows the AKS cluster to access other Azure resources securely.

- Create an AKS cluster using the [`az aks create`][az-aks-create] command.

    ```azurecli-interactive
    az aks create \
     --location $MY_LOCATION \
     --name $MY_CLUSTER_NAME \
     --tier standard \
     --resource-group $MY_RESOURCE_GROUP_NAME \
     --network-plugin azure  \
     --node-vm-size Standard_D4_v3 \
     --node-count 3 \
     --auto-upgrade-channel stable \
     --node-os-upgrade-channel  NodeImage \
     --attach-acr ${MY_ACR_REGISTRY} \
     --enable-oidc-issuer \
     --enable-workload-identity \
     --enable-addons azure-keyvault-secrets-provider \
     --zones 1 2 3 \
     --generate-ssh-keys \
     --output table
    ```

    Example output:
    <!-- expected_similarity=0.5 -->
    ```output
    Kind    KubernetesVersion    Location    MaxAgentPools    Name         NodeResourceGroup                         ProvisioningState    ResourceGroup       ResourceUid               SupportPlan
    -----------------------------------------------------------------------  --------------------------  ----------------------  ----------------------------------  ------------------------------------  -------------------------  ------------  ----------------------------------------------------------------  ------  -------------------  ----------  ---------------  -----------  ----------------------------------------  -------------------  ------------------  ------------------------  ------------------
    cluster-ak-myresourcegroup--9b70ac-hhrizake.portal.hcp.eastus.azmk8s.io  1.28.9                      False                   cluster-ak-myResourceGroup--9b70ac  a0a0a0a0-bbbb-cccc-dddd-e1e1e1e1e1e1  False                      True          cluster-ak-myresourcegroup--9b70ac-hhrizake.hcp.eastus.azmk8s.io Base     1.28                 eastus      100              cluster-aks  MC_myResourceGroup-rg_cluster-aks_eastus  Succeeded            myResourceGroup-rg  b1b1b1b1-cccc-dddd-eeee-f2f2f2f2f2f2  KubernetesOfficial
    ```

## Get the identity ID and object ID for the Azure Key Vault Secrets provider

- Get the identity ID and the object ID created by the Azure Key Vault Secrets provider add-on using the [`az aks show`][az-aks-show] command.

    ```azurecli-interactive
    export userAssignedIdentityID=$(az aks show --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_CLUSTER_NAME --query addonProfiles.azureKeyvaultSecretsProvider.identity.clientId --output tsv)
    export userAssignedObjectID=$(az aks show --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_CLUSTER_NAME --query addonProfiles.azureKeyvaultSecretsProvider.identity.objectId --output tsv)
    ```

## Assign the `AcrPull` role to the kubelet identity

- Assign the `AcrPull` role to the kubelet identity using the [`az role assignment create`][az-role-assignment-create] command.

    ```azurecli-interactive
    export KUBELET_IDENTITY=$(az aks show --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_CLUSTER_NAME --output tsv --query identityProfile.kubeletidentity.objectId)
    az role assignment create \
      --assignee ${KUBELET_IDENTITY} \
      --role "AcrPull" \
      --scope ${MY_ACR_REGISTRY_ID} \
      --output table
    ```

    Example output:
    <!-- expected_similarity=0.8 -->
    ```output
    CreatedBy                             CreatedOn                         Name                                  PrincipalId                           PrincipalName                         PrincipalType     ResourceGroup       RoleDefinitionId                                                                                                                            RoleDefinitionName    Scope                                                                                                                                                        UpdatedBy                             UpdatedOn
    ------------------------------------  --------------------------------  ------------------------------------  ------------------------------------  ------------------------------------  ----------------  ------------------  ------------------------------------------------------------------------------------------------------------------------------------------  --------------------  -----------------------------------------------------------------------------------------------------------------------------------------------------------  ------------------------------------  --------------------------------
    bbbb1b1b-cc2c-dd3d-ee4e-ffffff5f5f5f  2024-06-11T09:41:36.631310+00:00  00aa00aa-bb11-cc22-dd33-44ee44ee44ee  aaaaaaaa-bbbb-cccc-1111-222222222222  bbbbbbbb-cccc-dddd-2222-333333333333  ServicePrincipal  myResourceGroup-rg  /subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/providers/Microsoft.Authorization/roleDefinitions/aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb  AcrPull               /subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/resourceGroups/myResourceGroup-rg/providers/Microsoft.ContainerRegistry/registries/mydnsrandomnamebbbhe  bbbb1b1b-cc2c-dd3d-ee4e-ffffff5f5f5f  2024-06-11T09:41:36.631310+00:00
    ```

## Create a node pool for the Valkey workload

In this section, we create a node pool dedicated to running the Valkey workload. This node pool has autoscaling disabled and is created with six nodes across two availability zones, because we want to have one secondary per primary in a different zone. The replicas are scheduled in the third zone.

- Create a new node pool using the [`az aks nodepool add`][az-aks-nodepool-add] command.

    ```azurecli-interactive
    while [ "$(az aks show --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_CLUSTER_NAME --output tsv --query provisioningState)" != "Succeeded" ]; do echo "waiting for cluster to be ready"; sleep 10; done

    az aks nodepool add \
        --resource-group $MY_RESOURCE_GROUP_NAME \
        --cluster-name  $MY_CLUSTER_NAME \
        --name valkey \
        --node-vm-size Standard_D4_v3 \
        --node-count 6 \
        --zones 1 2 3 \
        --output table
    ```

    Example output:
    <!-- expected_similarity=0.8 -->
    ```output
    Count    CurrentOrchestratorVersion    ETag                                  EnableAutoScaling    EnableCustomCaTrust    EnableEncryptionAtHost    EnableFips    EnableNodePublicIp    EnableUltraSsd    KubeletDiskType    MaxPods    Mode    Name    NodeImageVersion                          OrchestratorVersion    OsDiskSizeGb    OsDiskType    OsSku    OsType    ProvisioningState    ResourceGroup       ScaleDownMode    TypePropertiesType       VmSize           WorkloadRuntime
    -------  ----------------------------  ------------------------------------  -------------------  ---------------------  ------------------------  ------------  --------------------  ----------------  -----------------  ---------  ------  ------  ----------------------------------------  ---------------------  --------------  ------------  -------  --------  -------------------  ------------------  ---------------  -----------------------  ---------------  -----------------
    6        1.28.9                        aaaa0000-bb11-2222-33cc-444444dddddd  False                False                  False                     False         False                 False             OS                 30         User    valkey  AKSUbuntu-2204gen2containerd-202405.27.0  1.28                   128             Managed       Ubuntu   Linux     Succeeded            myResourceGroup-rg  Delete           VirtualMachineScaleSets  Standard_D4s_v3  OCIContainer
    ```

## Upload Valkey images to your Azure Container Registry

In this section, we download the Valkey image from Docker Hub and upload it to Azure Container Registry. This step ensures that the image is available in your private registry and can be used in your AKS cluster. We don't recommend consuming the public image in a production environment.

- Import the Valkey image from Dockerhub and upload it to your Azure Container Registry using the [`az acr import`][az-acr-import] command.

    ```azurecli-interactive
    az acr import \
        --name $MY_ACR_REGISTRY \
        --source docker.io/valkey/valkey:latest  \
        --image valkey:latest \
        --output table
    ```

:::zone-end

:::zone pivot="terraform"

## Clone the Terraform module

- Clone the git repository with the Terraform module.

    ```bash
    git clone https://github.com/Azure/terraform-azurerm-avm-res-containerservice-managedcluster.git
    cd terraform-azurerm-avm-res-containerservice-managedcluster/tree/stateful-workloads/examples/stateful-workloads-valkey
    ```

## Create a Terraform variables file

- Set Valkey variables by creating a `valkey.tfvars` file with the following contents. You can also provide your specific [variables](https://developer.hashicorp.com/terraform/language/values/variables) at this step:

    ```terraform
        acr_task_content = <<-EOF
        version: v1.1.0
        steps:
          - cmd: bash echo Waiting 10 seconds the propagation of the Container Registry Data Importer and Data Reader role
          - cmd: bash sleep 10
          - cmd: az login --identity
          - cmd: az acr import --name $RegistryName --source docker.io/valkey/valkey:latest --image valkey:latest
        EOF
        
        valkey_enabled = true
        node_pools = {
          valkey = {
            name       = "valkey"
            vm_size    = "Standard_DS4_v2"
            node_count = 3
            zones      = [1, 2, 3]
            os_type    = "Linux"
          }
        }
    ```

## Deploy the infrastructure

1. To deploy the infrastructure, run the Terraform commands. In this step, we set the required variables for deploying Valkey.

    ```bash
    terraform init
    export MY_RESOURCE_GROUP_NAME=myResourceGroup-rg
    export MY_LOCATION=centralus
    SECRET=$(openssl rand -base64 32)
    export TF_VAR_valkey_password=${SECRET}
    export TF_VAR_location=${MY_LOCATION}
    export TF_VAR_resource_group_name=${MY_RESOURCE_GROUP_NAME}
    terraform apply -var-file="valkey.tfvars"
    ```

    > [!NOTE]
    > In some cases, the container registry tasks that import Valkey images to the container registry might fail. For more information, see [container-registry-task]. In most cases, retrying resolves the problem.

1. Run the following command to export the Terraform output values as environment variables in the terminal to use them in the next steps:

    ```bash
    export MY_ACR_REGISTRY=$(terraform output -raw acr_registry_name)
    export MY_CLUSTER_NAME=$(terraform output -raw aks_cluster_name)
    ```

:::zone-end

## Next step

> [!div class="nextstepaction"]
> [Configure and deploy the Valkey cluster on AKS][deploy-valkey]

## Contributors

_Microsoft maintains this article. The following contributors originally wrote it:_

- Nelly Kiboi | Service Engineer
- Saverio Proto | Principal Customer Experience Engineer
- Don High | Principal Customer Engineer
- LaBrina Loving | Principal Service Engineer
- Ken Kilty | Principal TPM
- Russell de Pina | Principal TPM
- Colin Mixon | Product Manager
- Ketan Chawda | Senior Customer Engineer
- Naveed Kharadi | Customer Experience Engineer
- Erin Schaffer | Content Developer 2

<!-- External links -->
[install-helm]: https://helm.sh/docs/intro/install/
[install-docker]: https://docs.docker.com/get-docker/

<!-- Internal links -->
[valkey-solution-overview]: ./valkey-overview.md
[azure-free-account]: https://azure.microsoft.com/pricing/purchase-options/azure-account?cid=msft_learn
[install-azure-cli]: /cli/azure/install-azure-cli
[az-group-create]: /cli/azure/group#az-group-create
[az-keyvault-create]: /cli/azure/keyvault#az-keyvault-create
[az-acr-create]: /cli/azure/acr#az-acr-create
[az-aks-create]: /cli/azure/aks#az-aks-create
[az-aks-show]: /cli/azure/aks#az-aks-show
[az-role-assignment-create]: /cli/azure/role/assignment#az-role-assignment-create
[az-aks-nodepool-add]: /cli/azure/aks/nodepool#az-aks-nodepool-add
[az-acr-import]: /cli/azure/acr#az-acr-import
[deploy-valkey]: ./deploy-valkey-cluster.md
[container-registry-task]:/azure/container-registry/container-registry-faq#run-error-message-troubleshooting
