Table of Contents
Hashicorp Vault on Kubernetes
Summary: This wiki page shows how to install hashicorp vault in developer mode on kubernetes.
Date: 26 August 2026
Note that in this page we'll be deploying hashicorp Vault in “dev” mode. This requires no further setup, no state management, and no initialization. This is useful for experimenting with Vault without needing to unseal, store keys, etc. All data is lost on restart, so do not use dev mode for anything other than experimenting. See dev-server to know more.
This wiki page uses my public AKS cluster for as a target kubernetes cluster. However, it does not use aks specific items such as workload identity, as this page intends to do a kubernetes generic deployment.
For the local deployment I recommend to use this wsl setup, as it has all the required tooling such as kubectl and helm.
All the kubernetes manifests on this page are also available on github: hashicorp vault manifest
Deployment Vault
We'll use helm to do the installation. Make sure kubectl is connected to the correct cluster (KUBECONFIG=/mnt/c/Users/aks/.kube/aks_public_user.yaml), and add the hashicorp helm repository to your list of repositories.
helm repo add hashicorp https://helm.releases.hashicorp.com helm repo update helm install vault hashicorp/vault \ --set "server.dev.enabled=true" \ --set "ui.enabled=true" \ --set "injector.enabled=false" \ --set "csi.enabled=true" \ --namespace ops
Deployment CSI provider
As we've enabled the csi-driver in the vault configuration we need to make sure csi is indeed installed. Depending on your specific kubernetes setup, chances are that the driver is already installed but if not, you can use helm again to do the installation:
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts helm repo update helm install csi secrets-store-csi-driver/secrets-store-csi-driver \ --set "syncSecret.enabled=true"
Add Gateway API HTTP Route
To make sure we can access the vault without port forwarding we need to add a HTTP route resource to the Gateway.
Make sure to verify the service name and port for the vault and edit the manifest below if required: kubectl get service -n ops
apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: vault spec: parentRefs: - name: gateway hostnames: ["aks.getshifting.com"] rules: - matches: - path: type: PathPrefix value: /ui - path: type: PathPrefix value: /v1 backendRefs: - name: vault-ui port: 8200
Apply the manifest: kubectl apply -f vault-http-route.yaml -n ops.
Add a Secret
Now we can add a secret to the vault using the interface:
- Go to https://aks.getshifting.com/ui. Dismiss the security warning about the certificate as a self signed certificate is created in AKS With Public Gateway API - The ui opens in your browser. Use 'token' as the authentication method and 'root' as the token itself - Go to Secrets Engines → Secrets → Create secret
- Name: vaultsecret
- Secret data:
- username: sjoerd
- password: testpass123!
Noe we will also create a secret in the vault using the CLI:
kubectl exec -it vault-0 -n ops -- /bin/sh vault kv put secret/vaultsecretcli username=cli password="clipassword" vault kv get secret/vaultsecretcli exit
Configure Kubernetes Authentication
Now we need to configure the vault to use kubernetes authentication, so we can authenticate with Vault using a Kubernetes Service Account Token:
# Access the vault pod kubectl exec -it vault-0 -n ops -- /bin/sh # Enable the Kubernetes authentication method vault auth enable kubernetes # Configure the Kubernetes authentication method with the Kubernetes API address. It will automatically use the Vault pod's own service account token. echo $KUBERNETES_PORT_443_TCP_ADDR vault write auth/kubernetes/config \ kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
Create a Policy and Role
Create a policy named shift-app. This will be used to give a service account permission to read the vault's secrets.
Note that there is an extra /data between the secrets store and the name of the secret
# Access the vault pod kubectl exec -it vault-0 -n ops -- /bin/sh # Create the policy vault policy write shift-app - <<EOF path "secret/data/vaultsecretcli" { capabilities = ["read"] } path "secret/data/vaultsecret" { capabilities = ["read"] } EOF
Create a Kubernetes authentication role named shiftrole that binds this policy with a Kubernetes service account named shift-sa.
# Access the vault pod kubectl exec -it vault-0 -n ops -- /bin/sh # Create the role vault write auth/kubernetes/role/shiftrole \ bound_service_account_names=shift-sa \ bound_service_account_namespaces=ops \ policies=shift-app \ ttl=20m exit
Create Secretprovider
Now we can create a secretprovider so we can serve a password from the vault to a pod later on:
apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: shift-app-spc spec: provider: vault parameters: vaultAddress: "http://vault.ops:8200" roleName: "shiftrole" objects: | - objectName: "shift-password-cli" secretPath: "secret/data/vaultsecretcli" secretKey: "password" - objectName: "shift-username-cli" secretPath: "secret/data/vaultsecretcli" secretKey: "username"
Apply the secretprovider: kubectl apply -f shift-app-spc.yaml -n ops
Use the Secretprovider
To use the secretprovider we first need to create a serviceaccount with the name and in the namespace we configured earlier while making the authentication role for the vault: kubectl create serviceaccount shift-sa -n ops. Alternatively you can use a manifest:
apiVersion: v1 kind: ServiceAccount metadata: name: shift-sa namespace: ops
Then we can create a pod manifest in which we use the secretprovider to mount the secrets as a volume into the pod:
kind: Pod apiVersion: v1 metadata: name: alpine-spc spec: serviceAccountName: shift-sa restartPolicy: Never containers: - name: alpine-addon image: alpine:3.24.1 args: - |- apk update apk add --no-cache curl busybox-extras bash sleep infinity command: - "/bin/sh" - "-c" resources: requests: cpu: 100m memory: 128Mi limits: cpu: 250m memory: 256Mi volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "shift-app-spc"
Apply the pod: kubectl apply -f alpine-spc.yaml -n ops
Now we can check if the secrets from the vault are indeed available inside of the pod:
kubectl exec alpine-spc -n ops -- cat /mnt/secrets-store/shift-password-cli kubectl exec alpine-spc -n ops -- cat /mnt/secrets-store/shift-username-cli
Sync the SecretProvider to a Kubernetes Secret
The Secrets Store CSI Driver also supports syncing to Kubernetes secret objects. To add secret syncing, update the SecretProviderClass to add a secretObjects entry:
apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: shift-app-spc-sync spec: provider: vault secretObjects: - data: - key: password objectName: shift-password - key: username objectName: shift-username secretName: k8svaultsecret type: Opaque parameters: vaultAddress: "http://vault.ops:8200" roleName: "shiftrole" objects: | - objectName: "shift-password" secretPath: "secret/data/vaultsecret" secretKey: "password" - objectName: "shift-username" secretPath: "secret/data/vaultsecret" secretKey: "username"
Now, when you reference the SecretProviderClass, k8s secrets will also be created, which can also be loaded as an environment variable:
kind: Pod apiVersion: v1 metadata: name: alpine-spc-sync spec: serviceAccountName: shift-sa restartPolicy: Never containers: - name: alpine-addon image: alpine:3.24.1 args: - |- apk update apk add --no-cache curl busybox-extras bash sleep infinity command: - "/bin/sh" - "-c" resources: requests: cpu: 100m memory: 128Mi limits: cpu: 250m memory: 256Mi env: - name: SHIFT_PASSWORD valueFrom: secretKeyRef: name: k8svaultsecret key: password volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "shift-app-spc-sync"
Apply the manifests to verify:
kubectl apply -f shift-app-spc-sync.yaml -n ops kubectl apply -f alpine-spc-sync.yaml -n ops kubectl get secret k8svaultsecret -n ops kubectl exec alpine-spc-sync -n ops -- env | grep SHIFT_PASSWORD
This wiki has been made possible by:
