Integrate HashiCorp Vault with Kubernetes secrets management for secure container orchestration

Advanced 45 min Apr 19, 2026 166 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure HashiCorp Vault integration with Kubernetes using the Vault CSI driver and Secrets Operator for automated secret injection and synchronization. This setup enables secure secret management for containerized applications with dynamic secret rotation and policy-based access controls.

Prerequisites

  • Running Kubernetes cluster with admin access
  • HashiCorp Vault server deployed and accessible
  • Helm 3 installed and configured
  • kubectl configured for cluster access
  • Basic understanding of Vault policies and Kubernetes RBAC

What this solves

Managing secrets in Kubernetes clusters becomes complex when you need centralized secret storage, rotation, and audit trails across multiple environments. This tutorial integrates HashiCorp Vault with Kubernetes to provide secure secret injection into pods using the Vault CSI driver and automated secret synchronization with the Vault Secrets Operator. You'll configure Kubernetes authentication, deploy both integration methods, and establish policy-based access controls for production-ready secret management.

Prerequisites and requirements

You need a running Kubernetes cluster with administrative access and HashiCorp Vault server already deployed. Your cluster should have Helm 3 installed and configured. Basic familiarity with Vault policies and Kubernetes RBAC is recommended.

Note: This tutorial assumes you have a Vault server accessible from your Kubernetes cluster. If you need to set up Vault first, check our Vault installation guide.

Step-by-step configuration

Configure Vault Kubernetes authentication

Set up the Kubernetes authentication method in Vault to allow pods to authenticate using service account tokens.

export VAULT_ADDR="https://vault.example.com:8200"
export VAULT_TOKEN="your-vault-token"

Enable Kubernetes authentication method

vault auth enable kubernetes

Extract Kubernetes cluster information

Gather the necessary cluster details that Vault needs to validate service account tokens.

# Get the Kubernetes API server URL
KUBE_API_URL=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}')

Extract the service account token reviewer JWT

KUBE_SA_TOKEN=$(kubectl get secret -n kube-system -o jsonpath='{.items[?(@.metadata.annotations.kubernetes\.io/service-account\.name=="default")].data.token}' | base64 --decode)

Get the certificate authority

KUBE_CA_CERT=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 --decode)

Configure Vault with Kubernetes cluster details

Configure the Kubernetes authentication method with your cluster's connection information.

vault write auth/kubernetes/config \
    token_reviewer_jwt="$KUBE_SA_TOKEN" \
    kubernetes_host="$KUBE_API_URL" \
    kubernetes_ca_cert="$KUBE_CA_CERT" \
    disable_iss_validation=true

Create Vault policies for Kubernetes access

Define policies that control what secrets Kubernetes applications can access.

path "secret/data/myapp/*" {
  capabilities = ["read"]
}

path "database/creds/myapp-role" {
  capabilities = ["read"]
}

path "auth/token/renew-self" {
  capabilities = ["update"]
}
# Apply the policy to Vault
vault policy write myapp-policy /tmp/app-policy.hcl

Create Kubernetes authentication role

Configure a role that maps Kubernetes service accounts to Vault policies.

vault write auth/kubernetes/role/myapp-role \
    bound_service_account_names=myapp-sa \
    bound_service_account_namespaces=default,production \
    policies=myapp-policy \
    ttl=1h

Install Vault CSI driver

Deploy the Vault CSI driver using Helm to enable secret injection as mounted volumes.

# Add HashiCorp Helm repository
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

Install Vault CSI driver

helm install vault-csi-provider hashicorp/vault-csi-provider \ --namespace vault-system \ --create-namespace \ --set "vault.address=${VAULT_ADDR}"

Install Vault Secrets Operator

Deploy the Vault Secrets Operator for automatic Kubernetes secret synchronization.

# Install Vault Secrets Operator
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
    --namespace vault-secrets-operator-system \
    --create-namespace

Configure Vault connection for the operator

Create a VaultConnection resource that defines how the operator connects to Vault.

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
  name: default
  namespace: vault-secrets-operator-system
spec:
  address: https://vault.example.com:8200
  skipTLSVerify: false
kubectl apply -f vault-connection.yaml

Create service account for applications

Set up a Kubernetes service account that will authenticate with Vault.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: myapp-sa
  namespace: default
---
apiVersion: v1
kind: Secret
metadata:
  name: myapp-sa-token
  namespace: default
  annotations:
    kubernetes.io/service-account.name: myapp-sa
type: kubernetes.io/service-account-token
kubectl apply -f service-account.yaml

Create sample secrets in Vault

Add some test secrets to Vault that your applications will consume.

# Enable KV secrets engine if not already enabled
vault secrets enable -path=secret kv-v2

Create sample application secrets

vault kv put secret/myapp/config \ db_host="db.example.com" \ db_port="5432" \ api_key="super-secret-api-key" vault kv put secret/myapp/credentials \ username="myapp-user" \ password="secure-password-123"

Configure CSI driver secret injection

Create a SecretProviderClass that defines which secrets to mount and how to format them.

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-myapp
  namespace: default
spec:
  provider: vault
  parameters:
    vaultAddress: "https://vault.example.com:8200"
    roleName: "myapp-role"
    objects: |
      - objectName: "db_host"
        secretPath: "secret/data/myapp/config"
        secretKey: "db_host"
      - objectName: "db_port"
        secretPath: "secret/data/myapp/config"
        secretKey: "db_port"
      - objectName: "api_key"
        secretPath: "secret/data/myapp/config"
        secretKey: "api_key"
      - objectName: "username"
        secretPath: "secret/data/myapp/credentials"
        secretKey: "username"
      - objectName: "password"
        secretPath: "secret/data/myapp/credentials"
        secretKey: "password"
  secretObjects:
  - secretName: myapp-secret
    type: Opaque
    data:
    - objectName: "db_host"
      key: "DB_HOST"
    - objectName: "db_port"
      key: "DB_PORT"
    - objectName: "api_key"
      key: "API_KEY"
    - objectName: "username"
      key: "USERNAME"
    - objectName: "password"
      key: "PASSWORD"
kubectl apply -f secret-provider-class.yaml

Deploy application with CSI driver

Create a pod that uses the CSI driver to mount secrets from Vault.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-csi
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp-csi
  template:
    metadata:
      labels:
        app: myapp-csi
    spec:
      serviceAccountName: myapp-sa
      containers:
      - name: myapp
        image: nginx:alpine
        volumeMounts:
        - name: secrets-store
          mountPath: "/mnt/secrets"
          readOnly: true
        env:
        - name: DB_HOST
          valueFrom:
            secretKeyRef:
              name: myapp-secret
              key: DB_HOST
        - name: DB_PORT
          valueFrom:
            secretKeyRef:
              name: myapp-secret
              key: DB_PORT
        command: ["/bin/sh"]
        args: ["-c", "while true; do echo 'App running with secrets'; sleep 30; done"]
      volumes:
      - name: secrets-store
        csi:
          driver: secrets-store.csi.k8s.io
          readOnly: true
          volumeAttributes:
            secretProviderClass: "vault-myapp"
kubectl apply -f app-with-csi.yaml

Configure Vault Secrets Operator authentication

Create a VaultAuth resource for the Secrets Operator to authenticate with Vault.

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: default
  namespace: default
spec:
  vaultConnectionRef: vault-secrets-operator-system/default
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: myapp-role
    serviceAccount: myapp-sa
kubectl apply -f vault-auth.yaml

Create VaultStaticSecret for operator

Configure the Secrets Operator to sync static secrets from Vault to Kubernetes secrets.

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: myapp-config
  namespace: default
spec:
  vaultAuthRef: default
  mount: secret
  type: kv-v2
  path: myapp/config
  destination:
    name: myapp-config-secret
    create: true
  refreshAfter: 30s
  rolloutRestartTargets:
  - kind: Deployment
    name: myapp-operator
kubectl apply -f vault-static-secret.yaml

Deploy application using operator-managed secrets

Create a deployment that consumes secrets managed by the Vault Secrets Operator.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-operator
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp-operator
  template:
    metadata:
      labels:
        app: myapp-operator
    spec:
      serviceAccountName: myapp-sa
      containers:
      - name: myapp
        image: nginx:alpine
        env:
        - name: DB_HOST
          valueFrom:
            secretKeyRef:
              name: myapp-config-secret
              key: db_host
        - name: DB_PORT
          valueFrom:
            secretKeyRef:
              name: myapp-config-secret
              key: db_port
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: myapp-config-secret
              key: api_key
        command: ["/bin/sh"]
        args: ["-c", "while true; do echo 'Operator app running'; env | grep -E '(DB_|API_)'; sleep 30; done"]
        ports:
        - containerPort: 80
kubectl apply -f app-with-operator.yaml

Configure dynamic database secrets

Set up Vault to generate dynamic database credentials for enhanced security.

# Enable database secrets engine
vault secrets enable database

Configure database connection (example with PostgreSQL)

vault write database/config/myapp-db \ plugin_name=postgresql-database-plugin \ connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/myapp?sslmode=require" \ allowed_roles="myapp-role" \ username="vault" \ password="vault-db-password"

Create database role for dynamic credentials

vault write database/roles/myapp-role \ db_name=myapp-db \ creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \ default_ttl="1h" \ max_ttl="24h"

Create VaultDynamicSecret for database credentials

Configure the operator to manage dynamic database credentials with automatic rotation.

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
  name: myapp-db-creds
  namespace: default
spec:
  vaultAuthRef: default
  mount: database
  path: creds/myapp-role
  destination:
    name: myapp-db-secret
    create: true
  renewalPercent: 90
  rolloutRestartTargets:
  - kind: Deployment
    name: myapp-db
kubectl apply -f vault-dynamic-secret.yaml

Verify your setup

Check that both integration methods are working correctly and secrets are accessible.

# Verify CSI driver pods are running
kubectl get pods -n vault-system

Check Secrets Operator status

kubectl get pods -n vault-secrets-operator-system

Verify application pods are running

kubectl get pods -l app=myapp-csi kubectl get pods -l app=myapp-operator

Check that secrets were created

kubectl get secrets | grep myapp

Verify CSI-mounted secrets

kubectl exec deployment/myapp-csi -- ls -la /mnt/secrets kubectl exec deployment/myapp-csi -- cat /mnt/secrets/db_host

Check operator-managed secrets

kubectl get secret myapp-config-secret -o jsonpath='{.data}' | jq

View VaultStaticSecret status

kubectl describe vaultstaticsecret myapp-config

Check dynamic secret status

kubectl describe vaultdynamicsecret myapp-db-creds
Note: If you encounter authentication issues, verify that your service account token is properly bound to the Vault role and that the Kubernetes API server URL is accessible from Vault.

Advanced configuration options

Configure secret rotation and renewal

Set up automatic secret renewal to ensure credentials remain valid without manual intervention.

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: myapp-rotated-config
  namespace: default
spec:
  vaultAuthRef: default
  mount: secret
  type: kv-v2
  path: myapp/config
  destination:
    name: myapp-rotated-secret
    create: true
  refreshAfter: 300s
  rolloutRestartTargets:
  - kind: Deployment
    name: myapp-operator
  hmacSecretData: true

Implement namespace isolation

Configure separate Vault policies and roles for different Kubernetes namespaces to implement proper secret isolation. This integration works seamlessly with Vault dynamic database secrets for complete secret lifecycle management.

# Create namespace-specific policy
vault policy write production-policy - <Create namespace-specific role
vault write auth/kubernetes/role/production-role \
    bound_service_account_names=production-sa \
    bound_service_account_namespaces=production \
    policies=production-policy \
    ttl=30m

Security best practices

Enable audit logging

Configure Vault audit logging to track secret access patterns and maintain security compliance.

# Enable audit logging in Vault
vault audit enable file file_path=/vault/logs/audit.log

Verify audit logging is enabled

vault audit list

Implement least privilege access

Create granular policies that limit secret access to only what each application requires.

# Minimal policy for specific application
path "secret/data/myapp/database" {
  capabilities = ["read"]
}

path "secret/data/myapp/api" {
  capabilities = ["read"]
  allowed_parameters = {
    "version" = []
  }
}

Deny access to sensitive admin secrets

path "secret/data/admin/*" { capabilities = ["deny"] }
Security Note: Never store Vault tokens in Kubernetes secrets or ConfigMaps. Always use service account token authentication for production deployments.

Performance and scaling considerations

For high-throughput environments, configure connection pooling and caching to optimize secret retrieval performance. The CSI driver supports local caching to reduce Vault API calls.

csi:
  resources:
    requests:
      cpu: 50m
      memory: 128Mi
    limits:
      cpu: 50m
      memory: 128Mi
  
  volumeMounts:
    - name: cache
      mountPath: /vault/cache
      
  volumes:
    - name: cache
      emptyDir:
        sizeLimit: 1Gi

Common issues

Symptom Cause Fix
Pod fails to mount secrets Service account not bound to Vault role Verify role configuration with vault read auth/kubernetes/role/myapp-role
Authentication denied errors Incorrect Kubernetes API configuration Reconfigure with vault write auth/kubernetes/config
Secrets not syncing VaultConnection misconfigured Check connection status with kubectl describe vaultconnection
CSI driver pods crashlooping Insufficient RBAC permissions Verify CSI driver service account has required cluster roles
Dynamic secrets not rotating Database role misconfigured Test role with vault read database/creds/myapp-role
High latency secret retrieval No connection pooling configured Enable CSI driver caching and adjust refresh intervals

Next steps

Running this in production?

Want this managed for you? Running Vault and Kubernetes integration at scale adds complexity around certificate rotation, policy management, audit compliance, and 24/7 monitoring. See how we run infrastructure like this for European SaaS and fintech teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.