External Secrets Operator (ESO)
Automatically sync secrets from HashiCorp Vault into native Kubernetes Secrets.
Time: ~15 minutes Difficulty: Intermediate
What You Will Learn
Section titled “What You Will Learn”- External Secrets Operator: the bridge between external secret stores and Kubernetes
- SecretStore: connecting ESO to Vault
- ExternalSecret: declaring which Vault secrets become K8s Secrets
- Automatic sync and refresh intervals
datavsdataFromfor selective vs full secret extraction- Apps consume normal K8s Secrets with no Vault SDK needed
Prerequisites
Section titled “Prerequisites”the Vault demo must be running with secrets already stored. If you haven’t done that:
task demo -- vault# Then follow the "Store Secrets" section in demos/vault/README.mdWhy ESO
Section titled “Why ESO”The previous demo showed how pods can authenticate to Vault and read secrets via the API. That works, but it means every app needs Vault-aware code. ESO removes that coupling:
- Apps use standard
secretKeyRef, no Vault SDK or API calls - ESO handles authentication, syncing, and refresh in the background
- Changing the secret store (Vault to AWS Secrets Manager, for example) requires updating the SecretStore, not the application
Install ESO
Section titled “Install ESO”Navigate to the demo directory:
cd demos/external-secretskubectl apply -f manifests/namespace.yaml
helm repo add external-secrets https://charts.external-secrets.iohelm repo update
helm install external-secrets external-secrets/external-secrets \ --namespace eso-demo \ --set installCRDs=trueWait for the operator to be ready:
kubectl get pods -n eso-demo -wConnect ESO to Vault
Section titled “Connect ESO to Vault”Create a SecretStore that points to the Vault instance from demo 28:
kubectl apply -f manifests/secret-store.yamlVerify the connection:
kubectl get secretstore -n eso-demoThe STATUS column should show Valid.
Create ExternalSecrets
Section titled “Create ExternalSecrets”Selective extraction (pick specific keys)
Section titled “Selective extraction (pick specific keys)”kubectl apply -f manifests/external-secret-db.yamlThis pulls individual fields from secret/demo/database in Vault and maps them to specific keys in a K8s Secret.
Full extraction (all keys)
Section titled “Full extraction (all keys)”kubectl apply -f manifests/external-secret-api.yamlThis pulls all keys from secret/demo/api in Vault using dataFrom.extract.
Verify the sync
Section titled “Verify the sync”# Check ExternalSecret statuskubectl get externalsecrets -n eso-demo
# See the K8s Secrets that ESO createdkubectl get secrets -n eso-demo
# Read the synced valueskubectl get secret db-credentials -n eso-demo -o jsonpath='{.data.DB_PASSWORD}' | base64 -d && echokubectl get secret api-credentials -n eso-demo -o jsonpath='{.data.key}' | base64 -d && echoThe values match what you stored in Vault in demo 28.
Deploy an App That Uses the Secrets
Section titled “Deploy an App That Uses the Secrets”kubectl apply -f manifests/app.yamlCheck the app logs:
kubectl logs deploy/demo-app -n eso-demoThe app reads database and API credentials from normal K8s Secrets. It has no idea Vault exists. If you swap Vault for AWS Secrets Manager tomorrow, you update the SecretStore and the app never changes.
Test Automatic Sync
Section titled “Test Automatic Sync”Update a secret in Vault and watch ESO sync the change:
# Update the password in Vaultkubectl exec -it vault-0 -n vault-demo -- \ vault kv put secret/demo/database \ username=appuser \ password=new-password-12345 \ host=postgres.example.com \ port=5432
# Wait for the refresh interval (30 seconds for db-credentials)sleep 35
# Check the K8s Secret was updatedkubectl get secret db-credentials -n eso-demo \ -o jsonpath='{.data.DB_PASSWORD}' | base64 -d && echo# Output: new-password-12345The K8s Secret is updated automatically. The app pod needs a restart to pick up the new env var value (env vars don’t hot-reload, but volume-mounted Secrets do).
What is Happening
Section titled “What is Happening”manifests/ namespace.yaml # eso-demo namespace secret-store.yaml # Vault token Secret + SecretStore CR external-secret-db.yaml # ExternalSecret: selective field mapping external-secret-api.yaml # ExternalSecret: extract all fields app.yaml # App consuming the synced K8s SecretsThe full flow:
Vault (source of truth) | v ESO polls every refreshIntervalSecretStore (connection config) | vExternalSecret (declares what to sync) | v creates/updatesKubernetes Secret (native K8s object) | v consumed byPod (via secretKeyRef or volume mount)data vs dataFrom:
| Mode | Use Case | ExternalSecret Field |
|---|---|---|
data | Pick specific keys, rename them | data[].secretKey + remoteRef.property |
dataFrom.extract | Pull all keys from a path | dataFrom[].extract.key |
Experiment
Section titled “Experiment”-
Delete the K8s Secret and watch ESO recreate it:
Terminal window kubectl delete secret db-credentials -n eso-demokubectl get externalsecrets -n eso-demo -w# ESO recreates it within the refresh interval -
Create an ExternalSecret with a template to format the output:
Terminal window kubectl apply -f - <<'EOF'apiVersion: external-secrets.io/v1beta1kind: ExternalSecretmetadata:name: db-connection-stringnamespace: eso-demospec:refreshInterval: 30ssecretStoreRef:name: vault-backendkind: SecretStoretarget:name: db-connection-stringdata:- secretKey: DSNremoteRef:key: demo/databaseproperty: username- secretKey: PASSWORDremoteRef:key: demo/databaseproperty: password- secretKey: HOSTremoteRef:key: demo/databaseproperty: hostEOF -
Check the ESO controller logs for sync activity:
Terminal window kubectl logs -l app.kubernetes.io/name=external-secrets -n eso-demo --tail=20
Cleanup
Section titled “Cleanup”helm uninstall external-secrets -n eso-demokubectl delete namespace eso-demo
# Also clean up Vault from demo 28 if donehelm uninstall vault -n vault-demokubectl delete namespace vault-demoFurther Reading
Section titled “Further Reading”See docs/deep-dive.md for a detailed explanation of ClusterSecretStore for multi-namespace access, PushSecret for writing back to Vault, secret templates with Go templating, multiple providers (AWS, GCP, Azure), generator CRDs, and migration from sealed-secrets.
Next Step
Section titled “Next Step”Move on to Tekton Basics to build cloud-native CI/CD pipelines.