HashiCorp Vault can hold API keys, database passwords, tokens, and other sensitive values so they never appear in Git or Helm values. A typical Kubernetes pattern is: Vault (KV v2) → External Secrets Operator → Kubernetes Secret → Pods (env or volumes). Any workload that reads from a Secret can use this flow; you only align Vault paths and ExternalSecret fields with your app’s expectations.
Role in the stack#
flowchart LR
subgraph git["Git"]
VY[Helm values / manifests]
NS[Namespace and wiring]
end
subgraph cluster["Cluster"]
V[Vault KV v2]
ESO[External Secrets Operator]
KS[Kubernetes Secret]
end
VY --> V
NS --> V
ESO -->|Vault API + token| V
ESO --> KS
Vault must be running and unsealed before ClusterSecretStore and ExternalSecret resources can sync. In GitOps setups, install Vault first, then External Secrets Operator, then applications that declare ExternalSecret objects (or use equivalent sync waves / dependencies).
What you store in Vault#
Use the KV secrets engine (this cluster uses v2 under a mount such as secret). Each secret is a set of key–value fields at a logical path. Convention helps operations:
- Prefix by scope: e.g.
team/product,environment/app, orproject/serviceso paths stay unique and policies can match prefixes. - Stable field names: use the same property names you will reference in
ExternalSecretremoteRef.property(for exampleDATABASE_URL,API_KEY). - One path per logical secret (per app, per environment, or per credential bundle), depending on how you want to rotate and audit.
Example (paths and keys are illustrative):
vault kv put secret/prod/checkout-service DATABASE_URL='…' API_KEY='…'Anything that needs to read these values through ESO must be allowed by Vault policy on that path (see below).
Deploying Vault on Kubernetes#
Install Vault with Helm or your GitOps tool. Common choices:
- Standalone with a persistent volume for production-like data retention.
- TLS: internal-only clusters often use plain HTTP to the Vault Service; use TLS if the API is exposed or policy requires it.
- Injector: optional; if you only sync via External Secrets Operator, you can disable the sidecar injector.
- UI: optional, for operators.
Pin chart and Vault versions to match your Kubernetes version (kubeVersion in the Helm chart).
Bootstrap after install#
Initialize and unseal Vault (HashiCorp quick start). Protect unseal keys and root token.
Enable KV v2 on the mount your
ClusterSecretStorewill use (for examplesecretatpath: secret,version: v2). The mount path must match what External Secrets Operator expects in its Vault provider config.Write secrets at paths your applications will reference from
ExternalSecretresources:
vault kv put secret/<env>/<app-name> <KEY>=<value> [<KEY2>=<value2> …]Policies: define a policy that grants
read(andlistif needed) on exactly the KV paths ESO should read. Attach that policy to a token or identity used by ESO (see the External Secrets Operator page for storing the token in Kubernetes).Least privilege: prefer narrow policies per environment or service family; avoid a single token that can read the entire KV tree unless operational needs justify it.
Operations#
kubectl -n <vault-namespace> get pods
# Confirm unsealed; spot-check: vault kv get secret/<path>If Vault is sealed, External Secrets Operator cannot sync; store conditions will show connection or permission errors until Vault is unsealed.
Adding a new application#
- Choose a Vault path and field names (see naming above).
- Write the secret in Vault and extend the ESO token’s policy if the path was not covered before.
- In the app’s namespace, add an
ExternalSecretthat maps those paths and properties to the KubernetesSecretkeys your Deployment or Helm chart expects (External Secrets Operator).
No change to Vault’s Helm values is required for each new app unless you add new mounts or auth methods.
Related#
- External Secrets Operator —
ClusterSecretStore,ExternalSecret,vault-eso-token, troubleshooting.