This is part of the Vault + Kubernetes Integration Guide. Return to the main guide for the full architecture overview.
The Vault CSI Provider uses the Kubernetes Secrets Store CSI Driver to mount Vault secrets directly as ephemeral volumes — no sidecar containers needed.
How It Differs from Agent Injector
| Aspect | Agent Injector | CSI Provider |
|---|---|---|
| Architecture | Sidecar per pod | DaemonSet per node |
| Resource usage | Higher (per-pod) | Lower (per-node) |
| Dynamic secrets | ✅ Full renewal | ❌ Static at mount |
| Templating | ✅ Advanced Go templates | ❌ Raw key-value only |
| Secret rotation | ✅ Automatic | ❌ Requires pod restart |
Best for: Workloads needing simple key-value secrets without dynamic renewal, and teams wanting lower resource overhead.
Installation
Step 1: 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 \
--set enableSecretRotation=true \
--set rotationPollInterval=120s
Step 2: Install the Vault Provider
helm install vault hashicorp/vault \
--namespace vault \
--create-namespace \
--set server.enabled=false \
--set injector.enabled=false \
--set csi.enabled=true
This deploys the Vault CSI provider as a DaemonSet that communicates with the CSI driver.
Usage
Step 1: Create a SecretProviderClass
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-webapp-secrets
namespace: webapp-ns
spec:
provider: vault
parameters:
vaultAddress: "https://vault.corp.com:8200"
roleName: "webapp-role"
objects: |
- objectName: "db_host"
secretPath: "secret/data/webapp/config"
secretKey: "db_host"
- objectName: "db_pass"
secretPath: "secret/data/webapp/config"
secretKey: "db_pass"
- objectName: "api_key"
secretPath: "secret/data/webapp/config"
secretKey: "api_key"
# Optional: also sync to a K8s Secret
secretObjects:
- secretName: webapp-synced-secret
type: Opaque
data:
- objectName: db_pass
key: DB_PASSWORD
Step 2: Mount in Your Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
namespace: webapp-ns
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
serviceAccountName: webapp-sa
containers:
- name: webapp
image: webapp:latest
volumeMounts:
- name: vault-secrets
mountPath: "/mnt/secrets"
readOnly: true
env:
# Use synced K8s Secret as env vars
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: webapp-synced-secret
key: DB_PASSWORD
volumes:
- name: vault-secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "vault-webapp-secrets"
Secrets appear as individual files under /mnt/secrets/:
/mnt/secrets/db_host
/mnt/secrets/db_pass
/mnt/secrets/api_key
When to Choose CSI Over Agent Injector
- ✅ You want lower resource overhead (DaemonSet vs per-pod sidecar)
- ✅ Your secrets are static and don’t need dynamic renewal
- ✅ You prefer the Kubernetes-native volume pattern
- ✅ You need to sync secrets to K8s Secrets for env var consumption
- ❌ Don’t use if you need dynamic database credentials or advanced templating
For dynamic secrets and advanced templating, see the Agent Injector Guide. For a fully Kubernetes-native CRD approach, see the Vault Secrets Operator Guide.