How to Set Up Secrets Management with HashiCorp Vault
A complete walkthrough deploying Vault, storing a secret, and retrieving it from a Kubernetes pod dynamically — instead of secrets sitting as plain base64 in a Kubernetes Secret object.
Kubernetes’ own Secret objects are only base64-encoded, not encrypted, by default — genuinely sensitive credentials benefit from a dedicated secrets manager like Vault, which adds real encryption, access policies, and audit logging.
Step 1: deploy Vault
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault -n vault --create-namespace
Step 2: initialize and unseal Vault
kubectl exec -n vault vault-0 -- vault operator init
kubectl exec -n vault vault-0 -- vault operator unseal <unseal-key-1>
kubectl exec -n vault vault-0 -- vault operator unseal <unseal-key-2>
kubectl exec -n vault vault-0 -- vault operator unseal <unseal-key-3>
Vault starts sealed — encrypted at rest with no way to decrypt anything until enough unseal keys (a threshold, typically 3 of 5 generated at init) are provided. Store these keys and the initial root token somewhere genuinely secure and separate from Vault itself — losing them means losing access to everything Vault protects.
Step 3: enable the Kubernetes authentication method
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc:443"
This lets pods authenticate to Vault using their own Kubernetes ServiceAccount token, rather than needing a separate Vault-specific credential distributed out of band.
Step 4: write a secret
vault kv put secret/myapp/database username="appuser" password="s3cr3t"
Step 5: create a policy scoping exactly what can be read
# myapp-policy.hcl
path "secret/data/myapp/*" {
capabilities = ["read"]
}
vault policy write myapp-policy myapp-policy.hcl
Scoping the policy to exactly the path a specific application needs — not a blanket allow — limits the blast radius if that application’s own credentials are ever compromised.
Step 6: bind the policy to a Kubernetes ServiceAccount
vault write auth/kubernetes/role/myapp \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=default \
policies=myapp-policy \
ttl=1h
Step 7: retrieve the secret from within a pod
# from inside the pod, using its own ServiceAccount token:
VAULT_TOKEN=$(vault write -field=token auth/kubernetes/login role=myapp jwt=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token))
vault kv get -field=password secret/myapp/database
In practice, this is usually handled by the Vault Agent injector, which automates fetching secrets and writing them to a file inside the pod, rather than requiring application code to call Vault’s API directly.
Step 8: enable the Vault Agent injector for automatic secret injection
helm upgrade vault hashicorp/vault --set injector.enabled=true
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "myapp"
vault.hashicorp.com/agent-inject-secret-db: "secret/myapp/database"
With these annotations on a pod spec, Vault’s injector adds a sidecar that authenticates and writes the secret to a local file the application container can read — no application code changes needed to integrate with Vault directly.
Why dynamic, scoped access beats a static Kubernetes Secret
A Kubernetes Secret is a static value, readable by anything with RBAC permission to view Secrets in that namespace, with no built-in expiration or fine-grained audit trail of who actually read it. Vault’s model — short-lived tokens, scoped policies per application, and a full audit log of every access — is a meaningfully stronger security posture for credentials that actually matter, at the cost of the additional operational complexity of running and unsealing Vault itself.