Install HashiCorp Vault on Kubernetes and Inject Secrets into Pods
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
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
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo updateStep 2: Create the vault namespace
kubectl create namespace vaultStep 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.
helm install vault hashicorp/vault \
--namespace vault \
--set server.dev.enabled=true \
--set server.dev.devRootToken=rootCheck the pods:
kubectl get pods -n vaultExpected 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:
kubectl exec -it vault-0 -n vault -- vault auth enable kubernetesExpected 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:
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:
kubectl exec -it vault-0 -n vault -- vault kv put secret/app/database \
password=supersecret \
username=appuserDev 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:
kubectl exec -it vault-0 -n vault -- vault kv get secret/app/databaseStep 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:
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:
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=24hStep 9: Create the service account
The pod must use the app service account to authenticate with Vault:
kubectl create serviceaccount app -n defaultStep 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:
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"]
22EOFStep 11: Verify secret injection
Wait for the pod to start, then check the injected file:
kubectl exec vault-test -n default -- cat /vault/secrets/database.txtExpected 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:
- The mutating webhook intercepted the pod creation request
- It injected the
vault-agent-initinit container and thevault-agentsidecar - The init container authenticated with Vault by presenting the pod's Kubernetes service account JWT; Vault verified it by calling the Kubernetes TokenReview API
- Vault validated the token against the Kubernetes API and issued a Vault token with
app-policy - The init container fetched the secret and wrote it to
/vault/secrets/database.txt - The app container started — the file was already there
- The
vault-agentsidecar 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.