Manage Secrets with External Secrets Operator
Manage secrets with External Secrets Operator — kubernetes tutorial. Learn how to securely sync external secrets into your cluster with hands-on steps, comparison, and troubleshooting.
Focus: manage secrets with external secrets operator
You've mastered deploying workloads and configuring networking, but one invisible threat can undo all that work: leaking secrets. Plain Kubernetes Secrets are base64-encoded, not encrypted, and managing rotation across dozens of microservices is a nightmare. That's where the External Secrets Operator (ESO) steps in — it syncs secrets from external vaults (like AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault) directly into your cluster, so you never have to hardcode sensitive data again.
The problem this lesson solves
Managing secrets directly in Kubernetes is inherently risky. A single commit to a public repo or a misconfigured RBAC rule can expose database passwords, API keys, or TLS certificates. Even with encryption at rest via kube-encrypt, you still face operational pain: rotating a password means updating every Secret YAML, reapplying it, and restarting pods. With External Secrets Operator, you decouple where secrets are stored from how pods consume them, following the principles of GitOps and zero-trust security.
In this lesson, you'll learn how ESO solves these exact pain points: - Centralized secret management — secrets live in a single source of truth (e.g., AWS Secrets Manager). - Automated synchronization — when a secret rotates in your vault, ESO updates the corresponding Kubernetes Secret within seconds. - No plaintext in manifests — your deployments reference a Secret name, never the actual value.
Core concept / mental model
Imagine a bridge between two islands: - Island A: Your external secrets store (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager). - Island B: Your Kubernetes cluster.
The External Secrets Operator is that bridge. It watches for custom resources called ExternalSecret and ClusterExternalSecret. When you apply an ExternalSecret manifest, the operator reaches out to your vault, fetches the secret value, and creates or updates a standard Kubernetes Secret in your namespace.
Key components
- ExternalSecret (namespace-scoped): maps vault secrets to a Kubernetes Secret in the same namespace.
- SecretStore / ClusterSecretStore: defines how to authenticate to the external vault (e.g., IAM role, static token, service principal).
- Provider: the backend service (AWS Secrets Manager, Azure Key Vault, etc.).
Pro tip: Think of
SecretStoreas a configuration object that tells ESO where to look, andExternalSecretas the mapping that tells it what to fetch and how to name it locally.
How it works step by step
- Install the operator in your cluster (via Helm, manifest, or OperatorHub). ESO runs as a Deployment with a controller loop.
- Define a SecretStore with your vault credentials. For example, an AWS Secrets Manager store might use an IAM role with IRSA (IAM Roles for Service Accounts).
- Apply an ExternalSecret resource specifying:
- The
secretStoreRef(which store to use). - The remote key path (e.g.,arn:aws:secretsmanager:us-east-1:123456789012:secret:prod-db-abc123). - How to map the remote secret's key/value pairs to the local Secret. - Monitor: ESO syncs the secret immediately, then periodically (default
refreshInterval: 1h) or on-demand via annotationeso.sync.io/force-sync. Pods read the resultingSecretas usual.
Detailed lifecycle for a rotation
- Your CI/CD updates the secret in AWS Secrets Manager.
- After the
refreshInterval, ESO compares the remote secret hash with the local one. - If changed, ESO patches the local Secret.
- Any pod that mounts that Secret will see the update after the kubelet syncs (usually within 60–90 seconds depending on mount propagation).
Hands-on walkthrough
Prerequisites
- A Kubernetes cluster (local like kind/Minikube or remote).
kubectlinstalled.- Access credentials to an external secrets store. For this example we'll use AWS Secrets Manager with an IAM role attached to the node (for simplicity).
Step 1: Install External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
-n external-secrets \
--create-namespace
Verify the operator pod is running:
kubectl get pods -n external-secrets
Step 2: Create a SecretStore pointing to AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-store
namespace: default
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef: # optional; if using IRSA, omit
name: aws-creds
key: access-key
secretAccessKeySecretRef:
name: aws-creds
key: secret-key
Security note: In production, use IRSA (IAM Roles for Service Accounts) instead of static keys. For a cluster with OIDC provider configured, you can set
clusterSecretStorewith an IAM role ARN in the pod's service account annotation.
Step 3: Create an ExternalSecret to sync a specific secret
Assume you have a secret in AWS called prod-db-password with a JSON value: {"password":"S3cur3P@ss!"}.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-password
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-store
kind: SecretStore
target:
name: db-secret # resulting K8s Secret name
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: prod-db-password
property: password # if the remote secret is JSON, pick a key
Apply it:
kubectl apply -f external-secret-db.yaml
Step 4: Verify the K8s Secret was created
kubectl get secret db-secret -o yaml
You'll see a password field. Decode it:
echo "YTM0Y3IzUFAhIQ==" | base64 -d # example output: S3cur3P@ss!
That's it — your pod can now use db-secret as a volume mount or environment variable.
Compare options / when to choose what
| Feature | External Secrets Operator | Sealed Secrets | Vault Sidecar |
|---|---|---|---|
| Source of truth | External vault (AWS, Azure, GCP, HashiCorp, etc.) | Local sealed secrets stored in git | External vault via sidecar injection |
| Rotation | Automatic via refreshInterval |
Manual (requires re-encrypting and re-applying) | Real-time via vault agent |
| Cluster requirements | Operator (single Pod) | Controller (single Pod) | Vault cluster + init containers |
| Complexity | Medium (set up store + external secret) | Low (encrypt with kubeseal) |
High (Vault cluster, auth, sidecar injection) |
| When to choose | Multi-cloud or existing vault with auto-rotation policies | GitOps-only shops without a vault | Already running Vault inside cluster and need dynamic secrets |
Pro tip: If you already have a vault like AWS Secrets Manager or Azure Key Vault, ESO is the natural fit. If you want a Git-native solution without a vault, Sealed Secrets is simpler.
Troubleshooting & edge cases
Common mistake: SecretStore not applying because of missing namespace / wrong auth
Symptom: ExternalSecret stays Status: Pending
Solution: Check the operator logs:
kubectl logs -n external-secrets deploy/external-secrets
Look for errors like access denied or secretRef not found. Ensure your IAM role or static keys have secretsmanager:GetSecretValue permission.
Edge case: The remote secret property only exists after rotation
If your ExternalSecret references a property that doesn't initially exist, ESO will report a sync failure. Use dataFrom with find to pull all keys dynamically:
dataFrom:
- extract:
key: prod-db-password # extracts all JSON keys
Edge case: SecretStore credentials expire
If you use static credentials (not recommended), rotate them by updating the referenced Secret. Then annotate the SecretStore to force a reload:
kubectl annotate secretstore aws-secrets-store eso.sync.io/force-sync=true
What you learned & what's next
You now understand how to manage secrets with External Secrets Operator — from installing the operator to creating SecretStore and ExternalSecret resources, and syncing sensitive data from an external vault into Kubernetes. You've seen how it automates rotation and separates the secret lifecycle from your application code.
Key takeaways:
- ESO bridges external vaults and Kubernetes Secrets.
- SecretStore defines where secrets live; ExternalSecret maps them to local Secrets.
- Use refreshInterval to control sync frequency.
- Prefer IRSA / workload identity over static keys.
- Troubleshoot by checking operator logs and SecretStore status.
Next up: Secrets Rotation and Immutable Secrets — learn how to force new pod creation when a secret changes and how to avoid stale secrets in long-running pods.
Practice recap
Practice task: Install ESO on your cluster (kind or Minikube). Create a SecretStore with a free trial AWS Secrets Manager or use the ESO mock provider for local testing. Then write an ExternalSecret that mirrors a mock secret and verify the resulting Kubernetes Secret exists. Try changing the remote value and waiting for the next sync interval to see automatic updates.
Common mistakes
- Forgetting to grant the IAM role or service account permission to read the specific secret ARN — the operator logs will show
AccessDeniedException. Always attach a policy withsecretsmanager:GetSecretValueon the secret ARN. - Using
spec.target.namethat matches an existing Secret but not settingcreationPolicy: Owner— ESO will not overwrite a Secret created manually. Either adopt it by settingcreationPolicy: Orphanor delete the old one. - Expecting immediate pod reload after secret rotation — ESO updates the Secret, but pods still read the old mount until the kubelet syncs (up to 90 seconds). Use a controller like Reloader or update a deployment annotation to trigger a rollout.
- Setting
refreshInterval: 0thinking it disables sync — it actually triggers sync every 0 seconds (infinite loop). Use a very large interval (e.g.,87600h) or omit to default to 1 hour.
Variations
- Instead of AWS Secrets Manager, you can use Azure Key Vault with
provider.azurekvand a service principal or managed identity. - For cluster-wide secrets, apply ClusterSecretStore and ClusterExternalSecret — these work across all namespaces without repeating the store configuration.
- Use
dataFromwithfindto dynamically pull all secrets matching a path pattern (e.g.,/prod/*) instead of listing each key manually.
Real-world use cases
- A fintech startup syncs database and payment gateway secrets from HashiCorp Vault to a Kubernetes cluster using External Secrets Operator, with automatic rotation every 6 hours.
- An e-commerce platform stores TLS certificates in AWS Secrets Manager and uses ESO to distribute them to ingress controllers across 20+ namespaces, eliminating manual certificate updates.
- A healthcare SaaS provider uses Azure Key Vault for API keys and connection strings, syncing via ESO to a multi-region AKS cluster to comply with HIPAA and reduce secret sprawl.
Key takeaways
- External Secrets Operator decouples secret storage from Kubernetes, letting you use your organization's existing vault (AWS, Azure, GCP, Vault).
- A
SecretStoredefines authentication and endpoint; anExternalSecretmaps remote secret data to a local Kubernetes Secret. - Secrets synchronize automatically based on
refreshInterval, enabling zero-touch rotation when the vault secret changes. - Prefer IAM roles / workload identity over static credentials for production secure setups.
- Pod restarts are not automatic on secret rotation; consider adding a controller like Reloader or updating annotations to restart pods.
- ESO supports both namespace-scoped and cluster-wide resources (
ClusterSecretStore,ClusterExternalSecret).
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.