# Hashicorp Vault on Kubernetes
**Summary**: This wiki page shows how to install hashicorp vault in developer mode on kubernetes. \\
**Date**: 26 August 2026 \\
{{tag>kubernetes helm}}
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 [[https://developer.hashicorp.com/vault/docs/concepts/dev-server |dev-server]] to know more.
> This wiki page uses my [[aksgatewayapi |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 |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: [[https://github.com/getshifting/getshifting/blob/main/k8s/app.hashicorp-vault-public-aks.yaml |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 [[aksgatewayapi]]
- 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 [[https://developer.hashicorp.com/vault/docs/auth/kubernetes |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 - <
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://