External Secrets Operator (ESO) watches ExternalSecret resources and materializes ordinary Kubernetes Secret objects from an upstream backend. With HashiCorp Vault as the backend, the same pattern works for any application: you store values in Vault, declare mappings in Git, and mount or env-inject the resulting Secret as usual.
Role in the stack#
flowchart LR
subgraph git["Git"]
CSS[ClusterSecretStore]
X[ExternalSecret per app]
end
subgraph cluster["Cluster"]
ESO[External Secrets Operator]
V[Vault KV v2]
KS[Kubernetes Secret]
P[Pods]
end
CSS --> ESO
X --> ESO
ESO -->|token auth| V
ESO -->|reconcile| KS
KS --> P
- ClusterSecretStore (cluster-scoped): one connection from the cluster to Vault (URL, mount, auth). Every namespace can reference it.
- ExternalSecret (namespace-scoped): one per app (or per secret bundle), mapping Vault paths and properties to keys on a target
Secret. - ESO reconciles on an interval and when specs change.
Install ESO after Vault is available and unsealable; install before workloads that depend on synced secrets, or ensure those workloads tolerate a short delay until the Secret exists.
Deploying the operator#
Install from the upstream Helm chart (or your GitOps pipeline). Typical options:
installCRDs: truesoExternalSecretandClusterSecretStoreAPIs exist.- Replicas: one is enough for small clusters; increase for availability if needed.
Version-pin the chart against your Kubernetes version.
ClusterSecretStore: point ESO at Vault#
Define a single cluster-wide store (name is arbitrary but must match ExternalSecret references). It must match how Vault is deployed:
server: Vault API URL reachable from the ESO pods (often in-cluster Service DNS).pathandversion: KV mount and v2 if you use KV v2.auth: often a token read from a KubernetesSecret(long-lived token with a tight Vault policy). Other auth methods (Kubernetes auth, AppRole, etc.) are supported by ESO; token auth is a common starting point.
Illustrative structure:
spec:
provider:
vault:
server: http://<vault-service>.<vault-namespace>.svc.cluster.local:8200
path: secret
version: v2
auth:
tokenSecretRef:
name: <token-secret-name>
namespace: <eso-namespace>
key: tokenAdjust server, mount path, and secret reference to your environment.
Bootstrap: Vault token for ESO#
Create a Kubernetes Secret in the ESO namespace holding the Vault token ESO should use:
kubectl create secret generic <token-secret-name> \
-n <eso-namespace> \
--from-literal=token=YOUR_VAULT_TOKENThe token must be allowed by Vault policy to read every KV path that any ExternalSecret in the cluster will use (or use multiple stores / auth methods per environment if you split access). After the token exists and Vault is unsealed, the ClusterSecretStore should become Ready.
ExternalSecret: per-application pattern#
For each application (or each distinct secret bundle), add an ExternalSecret in the same namespace as the workload:
| Field | Purpose |
|---|---|
secretStoreRef | kind: ClusterSecretStore, name: <your-store-name> |
target.name | Kubernetes Secret name to create or update |
data[] / dataFrom | Map Vault remoteRef (path + property) to secretKey on the Secret |
refreshInterval | How often to re-sync from Vault (rotation pickup) |
creationPolicy | Often Owner so the Secret is owned by the ExternalSecret resource |
Path alignment: remoteRef.key is the KV path (for KV v2, the path segment after the mount, e.g. prod/myapp). remoteRef.property is the field name inside that secret. They must match what you wrote with vault kv put.
Workload wiring: reference the generated Secret in your Deployment, StatefulSet, or Helm chart (envFrom, env, volumes) the same way you would for any hand-created secret.
Multiple applications#
- One
ClusterSecretStorecan back manyExternalSecretresources in different namespaces. - Each app team only needs RBAC to create
ExternalSecretin their namespace; they do not need direct Vault access if operations manage Vault paths and ESO token policy. - Tighten Vault policy so the ESO token can only read paths that are actually referenced in Git (or split stores/tokens per environment).
Operations#
kubectl get clustersecretstore
kubectl get externalsecret -A
kubectl describe externalsecret -n <namespace> <name>Rotate the Vault token used by ESO#
- Issue a new token (or update policy + token) in Vault.
- Update the Kubernetes token
SecretESO uses (same name and key as intokenSecretRef). - ESO reconciles automatically; restart ESO pods if you need an immediate reconnect.
Common issues#
| Symptom | Things to verify |
|---|---|
ClusterSecretStore not ready | Vault reachable and unsealed; token secret present and valid; network from ESO namespace to Vault. |
ExternalSecret not synced | remoteRef path and property match KV data; Vault policy allows read; target namespace exists. |
Generated Secret empty or stale | refreshInterval; verify with vault kv get on the same path and property. |
Related#
- HashiCorp Vault — KV layout, policies, unseal, adding paths for new apps.