Table of Contents
Creating a Helm Chart for Azure SecretProvider
Summary: This wiki page shows how to create a helm chart for the Azure Secretprovider.
Date: 8 February 2026
This is the wiki page on how the helm chart azure-secretprovider was created. The helm chart is used to create a SecretProviderClass manifest for AKS with workload identity enabled. After creating the chart and some example values file, I'll also show how to create a helm repository in Azure and how to publish the chart there. Finally, I'll also show an overview of how a secretprovider actually works in AKS with workload identity enabled.
Create a Helm Chart
Creating a Helm chart is very straightforward. If you already have a working environment with helm installed, you can simply run the following command to create a new chart:
helm create azure-secretprovider
This command will generate a directory structure for your Helm chart with some default files and folders. After that, and when looking at the documentation, the next step would be to remove all of the files and add your own. Now, creating a helm chart is very easy because all you need to do is adding a manifest file in the templates folder. That's all. Whenever you then do a helm install, it will just install that manifest. Of course, you can also use values, variables and more, which is what we'll do.
I used the following steps to create the chart, using the WSL installation I described earlier:
helm version
# Output: version.BuildInfo{Version:"v3.16.1", GitCommit:"5a5449dc42be07001fd5771d56429132984ab3ab", GitTreeState:"clean", GoVersion:"go1.22.7"}
helm create azure-secretprovider
A new folder is created called azure-secretprovider. After checking the files, I performed the following steps:
- Removed the empty directory
charts - Removed the directory
templates/tests - Updated the Chart.yaml file with relevant information about the chart
- From the
templatesfolder, removed all files except for_helpers.tplandNOTES.txt- Updated the
NOTES.txtfile with a link to this wiki page
- Created a new file
secretproviderclass.yamlin thetemplatesfolder- Created a template for the SecretProviderClass manifest, using values from
values.yaml
- Removed the content of the default
values.yamlfile and updated it with relevant parameters for the SecretProviderClass
To check the helm chart, see my github repository.
Validate the Helm Chart
First, we can validate the chart using helm lint:
$ helm lint ./azure-secretprovider ==> Linting ./azure-secretprovider 1 chart(s) linted, 0 chart(s) failed
Next, we can generate an empty template using helm template:
$ helm template azure-secretprovider ./azure-secretprovider --- # Source: azure-secretprovider/templates/secretproviderclass.yaml apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: azure-secretprovider spec: provider: azure parameters: keyvaultName: objects: | array: tenantId: usePodIdentity: "false" clientID:
So far, everything looks good. Now, let's create some values files.
Values Files
I have created various values files for different use cases.
Grafana
The following values file creates two secrets, one for the Grafana admin credentials and one for the Grafana Slack Webhook. The first secret has two keys, while the second secret has one key:
tenantId: 7e4an71d-a123-a123-a123-abcd12345678 clientID: acc78f4a-932a-415f-a77e-e7e1071a0161 keyvaultName: kv-euw-shift secrets: - Grafana-AdminPassword - Grafana-AdminUsername - Grafana-SlackWebHook secretObjects: - secretName: grafana-admin data: - key: admin-password objectName: Grafana-AdminPassword - key: admin-user objectName: Grafana-AdminUsername type: Opaque - secretName: grafana-slackwebhook data: - key: SLACKWEBHOOK objectName: Grafana-SlackWebHook type: Opaque
If we would use this values file, we would render the following manifest:
$ helm template azure-secretprovider ./azure-secretprovider --values ./values/azure-secretprovider-grafana.yaml --- # Source: azure-secretprovider/templates/secretproviderclass.yaml apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: azure-secretprovider spec: provider: azure parameters: keyvaultName: kv-euw-shift objects: | array: - | objectName: Grafana-AdminPassword objectType: secret - | objectName: Grafana-AdminUsername objectType: secret - | objectName: Grafana-SlackWebHook objectType: secret tenantId: 7e4an71d-a123-a123-a123-abcd12345678 usePodIdentity: "false" clientID: acc78f4a-932a-415f-a77e-e7e1071a0161 secretObjects: - data: - key: admin-password objectName: Grafana-AdminPassword - key: admin-user objectName: Grafana-AdminUsername secretName: grafana-admin type: Opaque - data: - key: SLACKWEBHOOK objectName: Grafana-SlackWebHook secretName: grafana-slackwebhook type: Opaque
If needed, we could override one of the settings in the values file using --set: $ helm template azure-secretprovider ./azure-secretprovider --values ./values/azure-secretprovider-grafana.yaml --set keyvaultName=kv-euw-shift-dev
Certificate
The following values file creates a secret for a certificate:
secrets: - getshifting-tls-cert - getshifting-tls-key secretObjects: - secretName: getshifting-tls data: - key: tls.crt objectName: getshifting-tls-cert - key: tls.key objectName: getshifting-tls-key type: kubernetes.io/tls
Creating a Helm Repository in Azure
To create a helm repository in Azure, we can use Azure Container Registry (ACR). ACR supports hosting helm charts, so we can push our chart to ACR and then use it as a helm repository. To create the repositor, we can go to Azure Cloud Shell and run the following commands:
# Variables $project = "acrshift" $rg = "rg-$project" $loc = "westeurope" # Create resource group az group create ` --name $rg ` --location $loc # Create Azure Container Registry az acr create ` --name "$project" ` --resource-group $rg ` --sku Basic ` --location $loc ` --public-network-enabled true
Push the Helm Chart to ACR
Now that we have created an Azure Container Registry, we can package our helm chart and push it to the ACR. Let's first package the chart:
helm package ./azure-secretprovider
Note that in the output of the command, you will see the path to the generated .tgz file. This is the file that we will push to ACR.
Next, we can push the chart to ACR. To do so, let's login to the ACR helm repository, amd then push the chart:
az login # Make sure you're logged in to the correct subscription az account set --subscription "30b3c71d-a123-a123-a123-abcd12345678" # Login to ACR helm repository USER_NAME="00000000-0000-0000-0000-000000000000" ACR_NAME="acrshift" PASSWORD=$(az acr login --name "$ACR_NAME" --expose-token --output tsv --query accessToken --only-show-errors) echo "$PASSWORD" | helm registry login "$ACR_NAME.azurecr.io" --username $USER_NAME --password-stdin # Push the chart to ACR helm push azure-secretprovider-0.1.0.tgz oci://"$ACR_NAME".azurecr.io/helm
Using the Helm Chart from ACR
Once the chart is pushed to the ACR, we can check the ACR for existing charts and view their manifests:
# List the helm charts in the ACR $ az acr repository list --name $ACR_NAME [ "helm/azure-secretprovider" ] # Show the details of the helm chart in the ACR $ az acr repository show --name $ACR_NAME --repository helm/azure-secretprovider { "changeableAttributes": { "deleteEnabled": true, "listEnabled": true, "readEnabled": true, "writeEnabled": true }, "createdTime": "2026-02-06T16:32:05.1518971Z", "imageName": "helm/azure-secretprovider", "lastUpdateTime": "2026-02-06T16:32:05.3101879Z", "manifestCount": 1, "registry": "acrshift.azurecr.io", "tagCount": 1 }
To use the helm chart from ACR, we can add the ACR helm repository to our local helm client, and then install the chart from there:
# Add ACR helm repository helm repo add "$ACR_NAME" oci://"$ACR_NAME".azurecr.io/helm # Update helm repositories helm repo update # Install the chart from ACR using a local values file $ helm template azure-secretprovider oci://$ACR_NAME.azurecr.io/helm/azure-secretprovider --values ./values/azure-secretprovider-grafana.yaml Pulled: acrshift.azurecr.io/helm/azure-secretprovider:0.1.0 Digest: sha256:e10671232d69e5da0d662fd2c7bacfb621caf04a100d545334bf96b045d5315c --- # Source: azure-secretprovider/templates/secretproviderclass.yaml apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: azure-secretprovider spec: provider: azure parameters: keyvaultName: kv-euw-shift objects: | array: - | objectName: Grafana-AdminPassword objectType: secret - | objectName: Grafana-AdminUsername objectType: secret - | objectName: Grafana-SlackWebHook objectType: secret tenantId: 7e4an71d-a123-a123-a123-abcd12345678 usePodIdentity: "false" clientID: acc78f4a-932a-415f-a77e-e7e1071a0161 secretObjects: - data: - key: admin-password objectName: Grafana-AdminPassword - key: admin-user objectName: Grafana-AdminUsername secretName: grafana-admin type: Opaque - data: - key: SLACKWEBHOOK objectName: Grafana-SlackWebHook secretName: grafana-slackwebhook type: Opaque
As you can see from the 'Pulled' output, the chart is now pulled from the Azure Container Registry, and then together with the values file successfully rendered.
How does the SecretProviderClass work in AKS with Workload Identity Enabled
Now that we have created a helm chart for creating an Azure Secret Provider targeted on AKS with workload identity enabled, I also want to briefly show how the secretprovider works. Because of the workload identity and the central role of a secret provider, I created a diagram to show how the secretprovider works in AKS with workload identity enabled:
As you may note, the overview is for a secretprovider for grafana. This allowed for something extra, because in this setup grafana is also configured to use workload identity to access Azure monitor.
Note that the helm chart for Grafana is listed as a mix of the helm chart and the rendered manifest. This way it shows exactly what values are important for this setup.
Troubleshooting the SecretProviderClass
When troubleshooting the SecretProviderClass, there are a few steps that you should usually take to find the cause. First, you need to find the node where the pod is running that wants to use the SecretProviderClass. Next, you need the logs of the aks-secrets-store-csi-driver pod that is running on that node. In the below example, we'll use grafana again:
# Get the node where the pod is running that wants to use the SecretProviderClass kubectl get pod <pod-name> -o wide | sls grafana # From the output, get all the pods running on the same node kubectl get pods -o wide | sls <node-name> # Get the logs of the aks-secrets-store-csi-driver pod that is running on that node kubectl logs <aks-secrets-store-csi-driver-pod-name> -n kube-system
Note that these commands are done on powershellm with sls (Select-String) to filter the output. You can also do this in bash with grep.
Below we'll handle some of the errors I've encountered over time.
Secrets namespace/secrets-store-creds not found
The error:
E0901 07:24:28.764891 1 reconciler.go:237] "failed to reconcile spc for pod" err="failed to get node publish secret namespace/secrets-store-creds, err: secrets \"namespace/secrets-store-creds\" not found" spc="vertex-secretprovider-grafana" pod="grafana-7fc7fb9fb4-f97hc" controller="rotation" E0901 07:24:28.764944 1 reconciler.go:365] "nodePublishSecretRef not found. If the secret with name exists in namespace, label the secret by running 'kubectl label secret secrets-store-creds secrets-store.csi.k8s.io/used=true -n namespace" err="secrets \"namespace/secrets-store-creds\" not found" name="secrets-store-creds" namespace="namespace"
This error is encountered when you missed one of the steps in the Secrets Store CSI Driver configuration. You can solve the error by labeling the secrets-store-creds secrets so the CSI driver knows it can use it:
kubectl label secret secrets-store-creds secrets-store.csi.k8s.io/used=true -n namespace
Failed to patch secret data
The error:
E0105 23:01:36.680891 1 reconciler.go:497] "failed to patch secret data" err="Secret \"grafana-admin\" not found" secret="namespace/grafana-admin" spc="namespace/vertex-secretprovider-grafana" controller="rotation"
This error happens when the secret that the SecretProviderClass is trying to create already exists, for example when it was manually created before. You could either delete the existing secret, or label the secret just as before:
kubectl label secret grafana-admin secrets-store.csi.k8s.io/managed=true -n nemspace
Next Steps
You can also use helmfile to generate the manifest for the secret provider. To see how to do this, check out my helmfile wiki page.
Useful Links
This wiki has been made possible by:

