
External Secrets Operator: A Kubernetes Guide for 2026
Learn to manage Kubernetes secrets with our complete guide to the External Secrets Operator. Covers architecture, CRDs, workflows, security, and best practices.
Your app is in Kubernetes. The database password lives somewhere else. A teammate rotated it yesterday, the Secret in the cluster changed, and the pods kept running with the old value until someone bounced the deployment by hand.
That's the gap most “hello world” guides skip.
External Secrets Operator has become a practical way to bridge external secret stores and Kubernetes-native consumption. It reads from external APIs and creates Kubernetes Secret objects from the desired state you define in ExternalSecret, with reconciliation driven by refreshInterval, spec changes, or metadata changes, as described in the official documentation. The happy path is straightforward. Production isn't.
This is the part new teams usually need help with: what ESO does, what it does not do, and which operational decisions matter once rotation, multi-namespace rollout, and debugging enter the picture.
Table of Contents
- Why Kubernetes Secret Management Needs a Better Way
- Understanding the External Secrets Operator
- Exploring the ESO Architecture and CRDs
- Syncing Secrets A Practical Workflow
- Securing and Operating ESO in Production
- ESO vs Other Secret Management Tools
- Migration and Troubleshooting Common Issues
Why Kubernetes Secret Management Needs a Better Way
Often, the starting point is whatever is fastest. Someone base64-encodes a value, checks a manifest into Git, and tells themselves it's temporary. Someone else runs kubectl create secret in staging, then repeats it in production, then forgets to document which values exist where.
That approach works right up until it doesn't. Rotation becomes a scavenger hunt. Auditing turns into guesswork. And once secrets leak into repos, chat history, shell history, or copied manifests, cleanup gets messy fast.

If you've ever treated .env files as the system of record, it's worth revisiting why that breaks down in shared environments. This breakdown of the env files security nightmare maps closely to the same failure modes teams hit in Kubernetes: duplicated values, unclear ownership, and secrets drifting across environments.
A lot of real incidents start with hardcoded credentials showing up where they shouldn't. Vulnsy's write-up on penetration testing hardcoded findings is a useful reminder that attackers don't need a complex path when secrets are already sitting in code, config, or image layers.
The real problem is workflow drift
Kubernetes Secret objects aren't the enemy. The problem is using them as the primary authoring surface for sensitive values. When humans are manually copying secrets into the cluster, every environment becomes its own snowflake.
That creates a few predictable problems:
- Manual creation doesn't scale: every namespace and environment needs repeated work.
- Git becomes a trap: teams try to version secret manifests, then spend time pretending base64 is protection.
- Rotation gets split across tools: one person rotates in the external system, another has to remember the cluster side.
Practical rule: If the source of truth for a secret lives outside Kubernetes, don't make people duplicate that value inside Kubernetes by hand.
External Secrets Operator earns its place by giving the cluster a declarative way to say, “fetch that value from the secret manager and materialize the Kubernetes secret my workloads expect.”
Understanding the External Secrets Operator
External Secrets Operator is easiest to understand if you stop thinking about it as a secret vault. It isn't one. It's a Kubernetes operator that exists to fetch values from an external provider and turn them into native Kubernetes Secret resources.
Why the operator pattern fits this problem
The official project docs describe ExternalSecret as a schema for fetching data from external APIs and making it available as Kubernetes Secrets, with support for syncing individual keys through spec.data or broader value sets through spec.dataFrom in the project documentation. That matters because applications in Kubernetes already know how to consume native Secret objects. ESO lets you keep that application model while moving secret ownership out of the cluster.
The operator pattern is a good fit here because secrets change over time. A one-time import isn't enough. You need a controller watching desired state and reconciling toward it repeatedly.
What ESO is really doing
Think of ESO like a tightly scoped assistant inside the cluster:
- It knows where to retrieve secret data from.
- It knows which remote values to fetch.
- It knows how to shape the resulting Kubernetes
Secret. - It checks again when the reconcile loop says it should.
That's the core behavior. Not “store once and forget.” Sync, check, update.
A simple mental model helps:
- You define access to a backend secret store.
- You define the remote keys you want.
- ESO fetches them.
- ESO creates or updates a Kubernetes
Secret. - Your workloads consume that Kubernetes
Secretthe normal Kubernetes way.
ESO bridges two worlds. Platform teams want centralized secret management in systems like AWS Secrets Manager, HashiCorp Vault, Google Secret Manager, or Azure Key Vault. Applications want native Kubernetes secrets.
That bridge is exactly why ESO is useful. It decouples secret administration from workload manifests without forcing every application team to change how they read config. For a new team member, that's the key idea to remember: the application still sees a Kubernetes Secret; the operator handles the retrieval and synchronization behind the scenes.
Exploring the ESO Architecture and CRDs
Once you start operating ESO for real, the important shift is to think in terms of resources and relationships, not just “the operator syncs a secret.”

The core objects
There are three objects you'll work with most often.
| Resource | Job | Scope |
|---|---|---|
SecretStore |
Defines how a namespace connects to an external provider | Namespace |
ClusterSecretStore |
Defines provider access for use across namespaces | Cluster |
ExternalSecret |
Defines what to fetch and what Kubernetes Secret to create |
Namespace |
SecretStore and ClusterSecretStore answer the connectivity question. They tell ESO how to talk to the external system. In practice, that means provider config and auth details.
ExternalSecret answers the intent question. It tells ESO what remote key to read, which properties to map, and what target Secret object should exist in Kubernetes.
How the pieces fit together
On screen, I usually explain it like this:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: app-secrets
namespace: payments
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
That object says, “in the payments namespace, here's how ESO should talk to the provider.”
Then the fetch instruction:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: payments
spec:
refreshInterval: 1h
secretStoreRef:
name: app-secrets
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: username
remoteRef:
key: prod/payments/database
property: username
- secretKey: password
remoteRef:
key: prod/payments/database
property: password
The resulting Kubernetes object is ordinary from the application's point of view:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: payments
type: Opaque
That simplicity is one of ESO's strengths. Your app team doesn't need provider-specific logic in every deployment.
Cluster-wide rollout with label selection
ESO stops being a simple sync helper and starts acting like platform plumbing. The API spec for ClusterExternalSecretSpec includes namespaceSelectors and namespaces for controlling where ExternalSecrets are created, plus refreshTime so the controller can re-check target namespaces and reconcile again, as shown in the API specification.
That capability matters when you're distributing the same secret pattern across many namespaces. Instead of cloning YAML into every namespace, platform teams can use labels to drive rollout.
For example, you might target all namespaces labeled for a given environment or tenant boundary. If labels change, ESO can expand or contract distribution through reconciliation rather than a batch of manual edits.
- Use namespace selectors for platform patterns: shared pull credentials, common integrations, and bootstrap config are good candidates.
- Avoid cluster-wide sprawl by default: not every secret belongs in a
ClusterSecretStoresetup. Shared access is convenient, but convenience widens blast radius. - Treat labels as access-affecting config: if labels determine propagation, changing labels is a sensitive operation.
A cluster-wide secret model is powerful, but it raises the cost of mistakes. One bad selector can fan out a secret farther than intended.
Syncing Secrets A Practical Workflow
The fastest way to get comfortable with External Secrets Operator is to build one realistic flow end to end. A database credential is the usual starting point because every team has one.
Here's the workflow in one view first.

If your backend store is AWS Secrets Manager, this AWS Secrets Manager integration reference is a helpful companion for understanding the upstream store side before you wire Kubernetes to it.
A minimal store and sync example
Assume the external provider already contains a secret like prod/orders/database with fields for username and password.
First, define provider access:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secretsmanager
namespace: orders
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
Then define what to sync:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: orders-db
namespace: orders
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: orders-db
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/orders/database
property: username
- secretKey: password
remoteRef:
key: prod/orders/database
property: password
Apply both:
kubectl apply -f secretstore.yaml
kubectl apply -f externalsecret.yaml
For a broader sync where you want all properties from a structured secret, dataFrom is often cleaner than listing every key manually.
A good walkthrough is worth seeing in action, so this demo helps if you want a visual pass before trying it yourself:
Verify the secret actually arrived
Many beginners stop too early. kubectl apply succeeding doesn't mean the fetch worked.
Check the custom resource first:
kubectl get externalsecret -n orders
kubectl describe externalsecret orders-db -n orders
Then check the generated Secret:
kubectl get secret orders-db -n orders
kubectl describe secret orders-db -n orders
What I look for during verification:
- Resource readiness: the
ExternalSecretshould show a healthy status condition. - Target secret existence: the Kubernetes
Secretshould exist in the expected namespace. - Expected keys: the generated secret should contain the keys your application references.
If the target Secret is missing, don't jump straight into the application manifest. The failure is almost always one of these: provider auth, wrong remote key, wrong property name, or a bad store reference.
How the application consumes it
Once ESO creates the Kubernetes Secret, workload consumption looks normal:
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
namespace: orders
spec:
selector:
matchLabels:
app: orders-api
template:
metadata:
labels:
app: orders-api
spec:
containers:
- name: app
image: orders-api:latest
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: orders-db
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: orders-db
key: password
That's one reason ESO fits Kubernetes teams well. The operational complexity sits in the secret supply chain, not in every app manifest.
Securing and Operating ESO in Production
The toy example is the easy part. Production quality comes from everything around it: auth boundaries, reconcile timing, RBAC, and the reality that synced secrets don't magically refresh inside already-running processes.
Lock down who can read what
Start with least privilege on the provider side. The credentials or workload identity used by a SecretStore should only be able to read the paths that store needs. If one namespace only needs app credentials for one service, don't hand it broad access to every secret in the account.
Then control who can create or edit ExternalSecret, SecretStore, and ClusterSecretStore resources. In practice, these CRDs are sensitive. A user who can point ESO at a broader store or a different remote key can influence what lands in-cluster.
Use Kubernetes RBAC with that in mind:
- Separate platform and app permissions: app teams may create
ExternalSecretobjects in their namespace, while platform teams own shared stores. - Be careful with cluster-scoped stores:
ClusterSecretStoreis convenient, but its convenience needs tighter governance. - Review secret definitions like code: treat store refs, selectors, and target names as security-relevant config.
For teams working through broader infrastructure risk, this guide on how to Reduce cloud security vulnerabilities is a practical complement to the secret-specific controls around ESO.
The pod restart gap everyone hits
This is the operational reality that trips people up most often. Akeyless notes that ESO updates Kubernetes Secrets when the refresh interval detects changes, but ESO itself does not restart pods. If your application reads secrets only at startup, you need additional tooling such as a reloader to trigger rollout when the secret changes, as explained in the Akeyless analysis.
That means the secret lifecycle has two parts:
- ESO synchronizes the updated value into the Kubernetes
Secret. - Something else causes workloads to reload or restart so they use it.
If you skip the second part, rotation is only half done.
Don't mark secret rotation as complete when the Kubernetes
Secretchanges. Mark it complete when the application is running with the new credential.
A common answer is Stakater Reloader or a similar controller that watches config and secret changes and triggers rolling updates. That's not optional for many apps. It's part of the production design.
Operational habits that prevent bad days
A few habits make ESO much more reliable:
- Choose
refreshIntervalintentionally: shorter intervals reduce delay after an upstream change, but they also increase polling frequency and make more visible any issues around app reload timing. - Document reload behavior per app: some apps can hot-reload mounted files, others need a pod restart, and many teams don't know which until an incident.
- Define ownership for every store and secret path: when sync fails, somebody needs to know whether the issue lives in Kubernetes, IAM, Vault policy, or the provider object itself.
- Keep sensitive app values in a managed secret system: a centralized vault with access controls and auditability is a much better home for production secrets than scattered environment files. This overview of secret variables management is a useful benchmark for what “managed” should look like.
ESO vs Other Secret Management Tools
ESO is strong, but it isn't the only pattern. The right choice depends on where you want the source of truth, how your workloads consume secrets, and whether you want Kubernetes Secret objects at all.
The project's maturity matters here too. The GitHub repository and docs show ESO has evolved into a broad multi-provider platform supporting backends including AWS Secrets Manager, HashiCorp Vault, Google Secret Manager, Azure Key Vault, IBM Cloud Secrets Manager, and Akeyless, among others, which is part of why it fits centralized and declarative secret delivery so well in the project repository.
How the models differ
Here's the comparison I use with teams.
Kubernetes Secret Management Approaches Compared
| Tool | Core Mechanism | Best For | Key Consideration |
|---|---|---|---|
| External Secrets Operator | Syncs values from an external provider into Kubernetes Secret objects |
Teams that want externalized secret management with native Kubernetes secret consumption | You still need to solve pod reload behavior after secret updates |
| Vault Agent Injector | Injects secrets into pods, often through sidecars or agent-based delivery | Workloads that need runtime injection and tighter Vault-centric integration | Adds pod-level complexity and changes the runtime model |
| Sealed Secrets | Encrypts secret manifests for storage in Git, then decrypts in-cluster | GitOps flows where teams want encrypted secret manifests in repositories | The cluster still becomes the destination for decrypted secret material |
| Secrets Store CSI Driver | Mounts secrets as files through CSI instead of creating standard Kubernetes Secret objects |
Workloads that prefer file mounts and want to avoid native secret objects in some cases | Application consumption pattern changes and isn't always a drop-in fit |
When ESO is the right fit
ESO is usually the right choice when these conditions are true:
- Your organization already trusts an external secret manager as the source of truth.
- Your workloads already consume Kubernetes
Secretobjects and you don't want to rewrite that pattern. - You want declarative YAML and controller-driven reconciliation rather than custom scripts.
Where ESO is weaker is also important:
- It doesn't complete the reload loop alone: you need companion tooling or app-level reload logic.
- It still materializes Kubernetes secrets: if your goal is to avoid that object type entirely, CSI-style delivery may fit better.
- It depends on provider access working cleanly: IAM, auth policies, and namespace boundaries become part of the day-to-day operating model.
That trade-off is why I don't frame ESO as “best.” I frame it as best aligned with the sync-to-native-secret model.
Migration and Troubleshooting Common Issues
The safest migration pattern is boring on purpose. Pick one non-critical secret first, create the external store entry, add the SecretStore, define the ExternalSecret, and point one workload at the generated Kubernetes Secret. Don't migrate everything in one sweep.
A low-risk migration path
A practical sequence looks like this:
- Mirror one existing secret into the external manager.
- Create ESO resources in the target namespace.
- Verify the generated Kubernetes
Secretbefore touching deployments. - Update one deployment to use the ESO-managed secret.
- Add reload handling if the app won't pick up updates dynamically.
This avoids a full cutover while you're still validating auth and naming patterns.
Why a secret is not syncing
When sync fails, start with the controller path, not the application.
- Describe the
ExternalSecret:kubectl describe externalsecret <name> -n <namespace> - Inspect controller logs: look for provider auth, missing key, or permission errors.
- Validate the store reference: confirm the
SecretStoreorClusterSecretStorename and kind match. - Check remote naming carefully: one wrong property field is enough to break the fetch.
- Confirm permissions end to end: the provider identity has to read the remote secret, not just authenticate.
Most ESO issues aren't mysterious. They're usually mismatched names, missing access, or an assumption that sync implies application reload.
If your team is still juggling .env files, pasted secrets, and inconsistent environment setup, EnvManager is a cleaner foundation. It gives you a centralized, encrypted place to manage environment variables and API secrets across projects and environments, with role-based access, audit trails, and simpler handoff from local development to CI/CD.
Prepared with the Outrank app