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.