HashiCorp Vault — CSI Provider
The Vault CSI Provider uses the Kubernetes Secrets Store CSI Driver to mount Vault secrets as files inside pods — without a sidecar agent.
How CSI Provider Works
1. Pod requests a SecretProviderClass volume
2. CSI Driver calls the Vault Provider (DaemonSet)
3. Vault Provider authenticates to Vault (K8s auth)
4. Vault Provider fetches secrets
5. Secrets are mounted as files in the pod
Install CSI Driver + Vault Provider
# Install the Secrets Store CSI Driver
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver \
--namespace kube-system \
--set syncSecret.enabled=true
# Install Vault CSI Provider
helm install vault hashicorp/vault \
--namespace vault \
--set "server.enabled=false" \
--set "injector.enabled=false" \
--set "csi.enabled=true"
SecretProviderClass
Define which secrets to mount:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-db-creds
spec:
provider: vault
parameters:
vaultAddress: "http://vault.vault.svc.cluster.local:8200"
roleName: "myapp"
objects: |
- objectName: "db-password"
secretPath: "secret/data/myapp/db"
secretKey: "password"
- objectName: "db-username"
secretPath: "secret/data/myapp/db"
secretKey: "username"
- objectName: "api-key"
secretPath: "secret/data/myapp/api"
secretKey: "key"
Use in a Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: myapp
containers:
- name: myapp
image: myapp:latest
volumeMounts:
- name: vault-secrets
mountPath: /mnt/secrets
readOnly: true
volumes:
- name: vault-secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: vault-db-creds
Secrets available at:
/mnt/secrets/db-password/mnt/secrets/db-username/mnt/secrets/api-key
Sync to Kubernetes Secrets
CSI can optionally create native K8s Secrets:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-db-creds
spec:
provider: vault
parameters:
vaultAddress: "http://vault.vault.svc.cluster.local:8200"
roleName: "myapp"
objects: |
- objectName: "db-password"
secretPath: "secret/data/myapp/db"
secretKey: "password"
- objectName: "db-username"
secretPath: "secret/data/myapp/db"
secretKey: "username"
# Sync to a K8s Secret
secretObjects:
- secretName: myapp-db-secret
type: Opaque
data:
- objectName: db-password
key: password
- objectName: db-username
key: username
Then use as environment variables:
containers:
- name: myapp
image: myapp:latest
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-db-secret
key: password
Agent Injector vs CSI Provider
| Feature | Agent Injector | CSI Provider |
|---|---|---|
| Sidecar required | Yes | No |
| Dynamic secret renewal | Yes (auto) | Limited |
| Custom templates | Yes (full Go template) | No |
| K8s Secret sync | No | Yes |
| Resource overhead | Higher (sidecar per pod) | Lower (DaemonSet) |
| Secret rotation | Automatic | On pod restart |
| Setup complexity | Simple (annotations) | More YAML (SecretProviderClass) |
Quick Reference
| Resource | Purpose |
|---|---|
SecretProviderClass |
Define which Vault secrets to mount |
csi.driver: secrets-store.csi.k8s.io |
CSI volume type in pod spec |
secretObjects |
Sync mounted secrets to K8s Secrets |
Next: External Secrets