External Secrets Operator: sync Vault into Kubernetes
Keep the source of truth in Vault and let the operator materialize Kubernetes Secrets your pods can mount.
Run kubectl get secret db-credentials -o yaml and you see a base64 password any namespace admin can decode. That is where most applications actually consume secrets — mounted as files, injected as env vars, copied into crash dumps. External Secrets Operator (ESO) keeps the authoritative copy in Vault (or AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) and materializes a native Kubernetes Secret on a refresh interval. Your Deployment stays dumb: it mounts db-credentials like always. The operator is the glue.
This note installs ESO, wires a namespace-scoped SecretStore that authenticates to Vault with Kubernetes auth, declares an ExternalSecret that maps remote paths to keys, and verifies sync status. For the Vault side — policies, Kubernetes auth roles, and least-privilege paths — pair this with Vault from dev to production.
ESO syncs copies into the cluster. Rotate at the source of truth.
ESO reconciles on an interval. A rotated value in Vault becomes a new Kubernetes Secret version — pods still need a reload strategy to pick it up.
Install ESO and confirm the controller
Pin a release version from the official Helm chart or static manifests — floating latest on something that writes Secrets is a supply-chain gamble. After install, the controller pod in external-secrets namespace should be Ready before you apply stores.
helm repo add external-secrets https://charts.external-secrets.iohelm install eso external-secrets/external-secrets -n external-secrets --create-namespacekubectl get pods -n external-secretsexternal-secrets-xxxx 1/1 RunningPoint a SecretStore at Vault with Kubernetes auth
A SecretStore is namespace-scoped — each team brings its own Vault role and mount. A ClusterSecretStore shares one backend cluster-wide; use it only when platform owns the trust boundary. The Kubernetes auth block tells ESO to exchange the pod's projected service account token for a Vault token limited by policy.
apiVersion: external-secrets.io/v1beta1kind: SecretStoremetadata:name: vault-prodnamespace: paymentsspec:provider:vault:server: "https://vault.acme.internal:8200"path: "secret"version: "v2"auth:kubernetes:mountPath: "kubernetes"role: "payments-eso"serviceAccountRef:name: external-secrets-sa
Declare what to sync with an ExternalSecret
The data block maps Kubernetes Secret keys to Vault paths and properties. Set refreshInterval short enough that rotation matters — one hour is a common starting point — and wire Stakater Reloader or a similar tool if your app caches env vars at startup. For bulk imports, dataFrom with extract pulls every key under a Vault path into one Secret — handy for legacy apps that expect a dozen env vars from a single mount.
Grant the ESO service account only the Vault policy it needs: read on database/payments, not database/*. A compromised payments namespace should not become a cluster-wide secret reader because someone copy-pasted an admin policy during the pilot.
apiVersion: external-secrets.io/v1beta1kind: ExternalSecretmetadata:name: payments-dbnamespace: paymentsspec:refreshInterval: 1hsecretStoreRef:name: vault-prodkind: SecretStoretarget:name: db-credentialscreationPolicy: Ownerdata:- secretKey: usernameremoteRef:key: database/paymentsproperty: username- secretKey: passwordremoteRef:key: database/paymentsproperty: password
kubectl apply -f secretstore.yaml -f externalsecret.yamlkubectl get externalsecret -n payments payments-dbNAME STORE REFRESH STATUS READYpayments-db vault-prod 1h SecretSynced Truekubectl get secret -n payments db-credentialsmaterialized Secret exists — know your etcd exposure windowWhere this goes next
Static passwords in KV are a stepping stone. Next: dynamic database credentials from Vault's database secrets engine so each pod gets a unique lease, and Kubernetes auth policies tight enough that a compromised namespace cannot read another team's paths. The Advanced secrets track covers injector vs CSI vs ESO trade-offs and rotation without midnight restarts.
Go deeper in a courseVault from dev to productionAuth methods, dynamic secrets, Kubernetes integration, and HA.View course