Configure Kubernetes secrets management with Vault integration for secure container orchestration

Advanced 45 min May 30, 2026 130 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up HashiCorp Vault Agent Injector to automatically inject secrets into Kubernetes pods without storing sensitive data in container images or configuration files. This production-grade approach replaces hardcoded secrets with dynamic, secure credential management.

Prerequisites

  • Running Kubernetes cluster
  • kubectl installed and configured
  • Helm 3 installed
  • Basic understanding of Kubernetes concepts

What this solves

Managing secrets in Kubernetes clusters creates security risks when credentials are stored in configuration files, environment variables, or container images. This tutorial shows you how to integrate HashiCorp Vault with Kubernetes to automatically inject secrets into pods at runtime, eliminating hardcoded credentials and providing centralized secret rotation.

Step-by-step configuration

Install Vault Agent Injector

Add the HashiCorp Helm repository and install Vault Agent Injector in your Kubernetes cluster.

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
helm install vault hashicorp/vault \
  --set="injector.enabled=true" \
  --set="server.dev.enabled=true" \
  --set="server.image.tag=1.15.2" \
  --namespace=vault-system \
  --create-namespace

Configure Vault server connection

Wait for the Vault pod to be ready and configure the connection to your Vault server.

kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=vault --namespace=vault-system --timeout=300s
kubectl exec -it vault-0 -n vault-system -- /bin/sh
vault auth enable kubernetes
vault secrets enable -path=secret kv-v2

Create Kubernetes authentication method

Configure Vault to authenticate requests from Kubernetes service accounts using the cluster's JWT tokens.

vault write auth/kubernetes/config \
  token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

Create test secrets in Vault

Store example database credentials that will be injected into your application pods.

vault kv put secret/myapp/db \
  username="dbuser" \
  password="supersecret123" \
  host="db.example.com" \
  port="5432"
vault kv put secret/myapp/api \
  token="api-token-xyz789" \
  endpoint="https://api.example.com"
exit

Create Vault policy for application access

Define a policy that allows your application to read specific secret paths.

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

path "secret/metadata/myapp/*" {
  capabilities = ["list"]
}
kubectl cp /tmp/myapp-policy.hcl vault-0:/tmp/myapp-policy.hcl -n vault-system
kubectl exec -it vault-0 -n vault-system -- vault policy write myapp /tmp/myapp-policy.hcl

Create Kubernetes service account and RBAC

Set up the service account that your application pods will use for Vault authentication.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault-auth
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: vault-auth-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
  • kind: ServiceAccount
name: vault-auth namespace: default
kubectl apply -f /tmp/vault-service-account.yaml

Configure Kubernetes auth role in Vault

Create a Vault role that maps the Kubernetes service account to the application policy.

kubectl exec -it vault-0 -n vault-system -- vault write auth/kubernetes/role/myapp \
  bound_service_account_names=vault-auth \
  bound_service_account_namespaces=default \
  policies=myapp \
  ttl=24h

Create secret injection templates

Define how Vault Agent will format and inject secrets into your pod filesystem.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "myapp"
        vault.hashicorp.com/agent-inject-secret-database: "secret/data/myapp/db"
        vault.hashicorp.com/agent-inject-secret-api: "secret/data/myapp/api"
        vault.hashicorp.com/agent-inject-template-database: |
          {{- with secret "secret/data/myapp/db" -}}
          DATABASE_URL="postgresql://{{ .Data.data.username }}:{{ .Data.data.password }}@{{ .Data.data.host }}:{{ .Data.data.port }}/myapp"
          {{- end -}}
        vault.hashicorp.com/agent-inject-template-api: |
          {{- with secret "secret/data/myapp/api" -}}
          API_TOKEN="{{ .Data.data.token }}"
          API_ENDPOINT="{{ .Data.data.endpoint }}"
          {{- end -}}
    spec:
      serviceAccountName: vault-auth
      containers:
      - name: app
        image: nginx:alpine
        command: ["/bin/sh"]
        args: ["-c", "while true; do echo 'App running...'; sleep 30; done"]
        env:
        - name: VAULT_SECRETS_PATH
          value: "/vault/secrets"

Deploy application with secret injection

Deploy the application and verify that Vault Agent automatically injects secrets into the pod.

kubectl apply -f /tmp/app-deployment.yaml
kubectl wait --for=condition=ready pod -l app=myapp --timeout=300s

Configure secret rotation

Set up automatic secret rotation by configuring Vault Agent to refresh secrets periodically.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-rotation
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp-rotation
  template:
    metadata:
      labels:
        app: myapp-rotation
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "myapp"
        vault.hashicorp.com/agent-pre-populate-only: "false"
        vault.hashicorp.com/agent-cache-enable: "true"
        vault.hashicorp.com/agent-cache-use-auto-auth-token: "true"
        vault.hashicorp.com/secret-volume-path: "/vault/secrets"
        vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/db"
        vault.hashicorp.com/agent-inject-template-config: |
          {{- with secret "secret/data/myapp/db" -}}
          {
            "database": {
              "username": "{{ .Data.data.username }}",
              "password": "{{ .Data.data.password }}",
              "host": "{{ .Data.data.host }}",
              "port": {{ .Data.data.port }}
            }
          }
          {{- end -}}
    spec:
      serviceAccountName: vault-auth
      containers:
      - name: app
        image: nginx:alpine
        command: ["/bin/sh"]
        args: ["-c", "while true; do cat /vault/secrets/config; sleep 60; done"]
        volumeMounts:
        - name: vault-secrets
          mountPath: /vault/secrets
          readOnly: true
kubectl apply -f /tmp/app-deployment-rotation.yaml

Verify your setup

Check that secrets are properly injected and accessible within your application pods.

kubectl get pods -l app=myapp
kubectl logs -l app=myapp
POD_NAME=$(kubectl get pods -l app=myapp -o jsonpath='{.items[0].metadata.name}')
kubectl exec $POD_NAME -- ls -la /vault/secrets/
kubectl exec $POD_NAME -- cat /vault/secrets/database
kubectl exec $POD_NAME -- cat /vault/secrets/api
kubectl exec -it vault-0 -n vault-system -- vault kv get secret/myapp/db

Advanced configuration

Configure production Vault server

For production use, deploy Vault in HA mode with proper TLS certificates and persistent storage.

global:
  enabled: true
  tlsDisable: false

injector:
  enabled: true
  replicas: 3
  logLevel: "info"
  agentImage:
    repository: hashicorp/vault
    tag: 1.15.2

server:
  image:
    repository: hashicorp/vault
    tag: 1.15.2
  ha:
    enabled: true
    replicas: 3
    raft:
      enabled: true
      setNodeId: true
      config: |
        ui = true
        listener "tcp" {
          tls_disable = 0
          address = "[::]:8200"
          cluster_address = "[::]:8201"
          tls_cert_file = "/vault/userconfig/vault-server-tls/tls.crt"
          tls_key_file = "/vault/userconfig/vault-server-tls/tls.key"
        }
        storage "raft" {
          path = "/vault/data"
        }
        service_registration "kubernetes" {}
helm upgrade vault hashicorp/vault \
  --namespace=vault-system \
  --values=/tmp/vault-production-values.yaml

Set up secret versioning and rollback

Configure Vault to maintain secret versions for rollback capabilities.

kubectl exec -it vault-0 -n vault-system -- vault kv metadata put -max-versions=5 secret/myapp/db
kubectl exec -it vault-0 -n vault-system -- vault kv put secret/myapp/db \
  username="dbuser" \
  password="newsecret456" \
  host="db.example.com" \
  port="5432"
kubectl exec -it vault-0 -n vault-system -- vault kv get -version=1 secret/myapp/db

Configure dynamic database secrets

Set up Vault to generate database credentials dynamically with automatic rotation.

kubectl exec -it vault-0 -n vault-system -- vault secrets enable database
kubectl exec -it vault-0 -n vault-system -- vault write database/config/postgresql \
  plugin_name="postgresql-database-plugin" \
  connection_url="postgresql://{{username}}:{{password}}@postgres.example.com:5432/myapp?sslmode=require" \
  allowed_roles="myapp-role" \
  username="vault-admin" \
  password="admin-password"
kubectl exec -it vault-0 -n vault-system -- vault write database/roles/myapp-role \
  db_name="postgresql" \
  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"

Security best practices

Enable Vault audit logging

Configure comprehensive audit logging for security compliance and monitoring.

kubectl exec -it vault-0 -n vault-system -- vault audit enable file file_path=/vault/logs/vault-audit.log
kubectl exec -it vault-0 -n vault-system -- vault write sys/config/auditing/request-headers/X-Forwarded-For hmac=false

Configure namespace isolation

Set up Vault namespaces to isolate secrets between different applications and environments.

kubectl exec -it vault-0 -n vault-system -- vault namespace create production
kubectl exec -it vault-0 -n vault-system -- vault namespace create staging

Implement secret access monitoring

Set up monitoring and alerting for secret access patterns using Vault metrics.

apiVersion: v1
kind: ConfigMap
metadata:
  name: vault-exporter-config
  namespace: vault-system
data:
  config.yml: |
    vault:
      addr: "https://vault.vault-system.svc.cluster.local:8200"
      token: "your-monitoring-token"
    metrics:
      - vault_core_unsealed
      - vault_runtime_alloc_bytes
      - vault_auth_method_login_total
kubectl apply -f /tmp/vault-monitoring.yaml

Common issues

SymptomCauseFix
Pod fails to start with init container errorsService account lacks proper RBAC permissionsVerify ClusterRoleBinding with kubectl get clusterrolebinding vault-auth-binding
Secrets not appearing in pod filesystemIncorrect Vault role configuration or annotationsCheck role binding with kubectl exec vault-0 -n vault-system -- vault read auth/kubernetes/role/myapp
Authentication failures in Vault logsJWT token validation issuesReconfigure auth method with vault write auth/kubernetes/config using correct cluster CA
Secret templates render as emptyPolicy doesn't allow access to secret pathUpdate policy with vault policy write myapp /path/to/policy.hcl
Vault Agent sidecar consuming high CPUAggressive secret renewal configurationAdjust vault.hashicorp.com/agent-cache-enable and renewal intervals

Next steps

Running this in production?

Need this managed? Running this at scale adds a second layer of work: capacity planning, failover drills, cost control, and on-call. See how we run infrastructure like this for European 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.