Security

Install HashiCorp Vault on Kubernetes and Inject Secrets into Pods

Advanced30 min to complete16 min read

Deploy HashiCorp Vault on Kubernetes with Helm, enable the Kubernetes auth backend, write a policy, and inject secrets into pods via the Vault Agent sidecar — without any application code changes.

Before you begin

  • A running Kubernetes cluster
  • kubectl configured with cluster-admin access
  • Helm 3 installed
  • Basic familiarity with Kubernetes service accounts
Vault
HashiCorp
Secrets Management
Kubernetes
Helm
Security

Kubernetes Secrets are base64-encoded, not encrypted, and anyone with namespace access can read them. Vault is the standard solution for teams that need actual secrets management: encryption at rest, fine-grained access policies, dynamic credentials, and a full audit log of every secret read.

This tutorial gets Vault running in your cluster in dev mode (for learning), enables the Kubernetes auth backend so pods can authenticate automatically, and injects secrets into a pod via the Vault Agent sidecar — without touching a single line of application code.

Step 1: Add the HashiCorp Helm repository

bash
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

Step 2: Create the vault namespace

bash
kubectl create namespace vault

Step 3: Install Vault in dev mode

Dev mode starts Vault pre-initialised and pre-unsealed with a root token of root. It's in-memory only — data is lost on pod restart. Use this to learn the auth configuration before setting up a production HA deployment.

bash
helm install vault hashicorp/vault \
  --namespace vault \
  --set server.dev.enabled=true \
  --set server.dev.devRootToken=root

Check the pods:

bash
kubectl get pods -n vault

Expected output:

NAME                                    READY   STATUS    RESTARTS
vault-0                                 1/1     Running   0
vault-agent-injector-7d8b9c8d9f-kx4p2  1/1     Running   0

The vault-agent-injector pod is the mutating webhook that intercepts pod creation and injects the Vault Agent sidecar.

Step 4: Enable the Kubernetes auth backend

Exec into the Vault pod to run Vault CLI commands:

bash
kubectl exec -it vault-0 -n vault -- vault auth enable kubernetes

Expected output:

Success! Enabled the kubernetes auth method at: kubernetes/

Step 5: Configure the Kubernetes auth backend

Tell Vault how to reach the Kubernetes API to validate service account tokens:

bash
kubectl exec -it vault-0 -n vault -- vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc:443"

This is sufficient for Vault 1.9.3+ running in-cluster — Vault automatically reads the CA certificate and reviewer JWT from the default service account mount. On Vault older than 1.9.3, you must also pass token_reviewer_jwt and kubernetes_ca_cert.

Step 6: Write a secret

Put a test secret at a path your application will read:

bash
kubectl exec -it vault-0 -n vault -- vault kv put secret/app/database \
  password=supersecret \
  username=appuser

Dev mode pre-mounts secret/ as a KV v2 engine. KV v2 appends /data/ to your path internally, which is why policies and injector annotations reference secret/data/app/database even though you typed secret/app/database above.

Verify it was stored:

bash
kubectl exec -it vault-0 -n vault -- vault kv get secret/app/database

Step 7: Create a Vault policy

Policies define what paths a role can access. This policy grants read access to secrets stored under the secret/app/ KV path. In KV v2 policies, you must use the secret/data/ prefix (the internal API path) even though the vault kv CLI uses the shorter secret/ form:

bash
kubectl exec -it vault-0 -n vault -- /bin/sh -c '
vault policy write app-policy - <<EOF
path "secret/data/app/*" {
  capabilities = ["read"]
}
EOF'

Expected output:

Success! Uploaded policy: app-policy

Step 8: Create an auth role

Bind the policy to a Kubernetes service account. Pods using the app service account in the default namespace will receive a Vault token with app-policy attached:

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

Step 9: Create the service account

The pod must use the app service account to authenticate with Vault:

bash
kubectl create serviceaccount app -n default

Step 10: Deploy a pod with secret injection

The Vault Agent Injector reads pod annotations and injects a sidecar that authenticates with Vault and writes secrets to /vault/secrets/ as files:

bash
1cat <<EOF | kubectl apply -f -
2apiVersion: v1
3kind: Pod
4metadata:
5  name: vault-test
6  namespace: default
7  annotations:
8    vault.hashicorp.com/agent-inject: "true"
9    vault.hashicorp.com/role: "app"
10    vault.hashicorp.com/agent-inject-secret-database.txt: "secret/data/app/database"  # KV v2 API path; includes /data/ segment
11    vault.hashicorp.com/agent-inject-template-database.txt: |
12      {{- with secret "secret/data/app/database" -}}
13      DB_USERNAME={{ .Data.data.username }}
14      DB_PASSWORD={{ .Data.data.password }}
15      {{- end }}
16spec:
17  serviceAccountName: app
18  containers:
19  - name: app
20    image: busybox
21    command: ["sleep", "3600"]
22EOF

Step 11: Verify secret injection

Wait for the pod to start, then check the injected file:

bash
kubectl exec vault-test -n default -- cat /vault/secrets/database.txt

Expected output:

DB_USERNAME=appuser
DB_PASSWORD=supersecret

The file is owned by the vault agent sidecar. The application reads it as a regular file — no Vault SDK, no environment variable injection, no application changes required.

Step 12: Understand the flow

What just happened:

  1. The mutating webhook intercepted the pod creation request
  2. It injected the vault-agent-init init container and the vault-agent sidecar
  3. The init container authenticated with Vault by presenting the pod's Kubernetes service account JWT; Vault verified it by calling the Kubernetes TokenReview API
  4. Vault validated the token against the Kubernetes API and issued a Vault token with app-policy
  5. The init container fetched the secret and wrote it to /vault/secrets/database.txt
  6. The app container started — the file was already there
  7. The vault-agent sidecar runs continuously, renewing the token and updating the file when the secret rotates

What you built

Vault is running in your cluster with the Kubernetes auth backend enabled. Any pod annotated with vault.hashicorp.com/agent-inject: "true" receives secrets as files — with zero application code changes. The sidecar handles token renewal and secret rotation automatically. To move to production, replace dev mode with the HA Vault Helm configuration and configure auto-unseal via AWS KMS or Azure Key Vault.

We built Podscape to simplify Kubernetes workflows like this — logs, events, and cluster state in one interface, without switching tools.

Struggling with this in production?

We help teams fix these exact issues. Our engineers have deployed these patterns across production environments at scale.