What is External Secrets Operator?
External Secrets Operator (ESO) is a Kubernetes operator that integrates external secret management systems like AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, Google Secret Manager, and many others directly into Kubernetes. It synchronizes secrets from these external providers into native Kubernetes Secrets, keeping them automatically in sync with their source of truth.
Unlike storing sensitive data directly in Kubernetes Secrets—where they exist as base64-encoded values in etcd—ESO maintains a live connection to your secret backend. It pulls secrets on demand or on a scheduled refresh interval, ensuring that applications always receive fresh credentials without manual intervention or CI/CD pipeline exposure.
ESO acts as a bridge between Kubernetes and external secret stores. The operator watches for custom resources called ExternalSecret and SecretStore, then fetches secrets from the configured provider and materializes them as standard Kubernetes Secret objects that pods can mount as environment variables or volume files.
Why External Secrets Operator Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Managing secrets in Kubernetes presents several challenges that ESO addresses directly:
- Secret drift elimination — When secrets are updated in an external provider, ESO automatically reflects those changes inside the cluster, removing the lag between rotation and consumption.
- Single source of truth — Teams can manage secrets in one centralized system (AWS Secrets Manager, Vault, etc.) while multiple Kubernetes clusters consume them consistently.
- Audit and compliance — External secret stores typically offer robust audit logging, whereas base64-encoded Kubernetes Secrets provide no access tracking. ESO lets you leverage the provider's audit capabilities.
- Reduced secret sprawl — Without ESO, secrets often end up duplicated across CI/CD pipelines, Helm values files, and developer workstations. ESO keeps secrets out of pipelines entirely.
- Automatic rotation — When credentials rotate in the external provider, ESO picks up the new values within the configured refresh interval, often without restarting pods (when mounted as volume files with auto-reload).
- Namespace-scoped or cluster-wide access — ESO supports both namespace-scoped
SecretStoreresources and cluster-wideClusterSecretStoreresources, giving platform teams fine-grained control.
Core Concepts and Architecture
ESO operates with three primary custom resource definitions (CRDs):
SecretStore
A namespace-scoped resource that defines how to connect to an external secret provider. It holds provider-specific configuration such as AWS credentials, Azure service principal details, or Vault connection parameters. Only ExternalSecret resources in the same namespace can reference a SecretStore.
ClusterSecretStore
A cluster-wide variant of SecretStore. Any ExternalSecret in any namespace can reference a ClusterSecretStore, making it ideal for platform teams that want to configure provider access once and let all namespaces consume secrets from it.
ExternalSecret
The resource that declares which secrets to fetch from a SecretStore or ClusterSecretStore. It specifies the target Kubernetes Secret name, the keys to retrieve from the provider, and optional transformation rules. The ESO controller reconciles this resource and creates or updates the corresponding Kubernetes Secret.
Controller Architecture
The ESO deployment consists of a single controller pod (when installed via Helm) that watches for ExternalSecret, SecretStore, and ClusterSecretStore resources. On reconciliation, the controller authenticates to the configured external provider using the credentials defined in the referenced store, fetches the requested secrets, and writes them into a Kubernetes Secret in the appropriate namespace. The controller also manages the lifecycle—deleting an ExternalSecret removes the corresponding Kubernetes Secret if the deletionPolicy is set accordingly.
Installation and Setup
The recommended installation method is via Helm. Add the ESO chart repository and install the operator:
# Add the External Secrets Operator Helm repository
helm repo add external-secrets https://charts.external-secrets.io
# Update your local Helm chart repository cache
helm repo update
# Install ESO into the external-secrets namespace
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--set installCRDs=true
Verify the installation by checking that the controller pod is running and the CRDs are registered:
# Check the controller pod status
kubectl get pods -n external-secrets
# Verify the CRDs are installed
kubectl get crd | grep external-secrets
You should see CRDs for secretstores.external-secrets.io, clustersecretstores.external-secrets.io, and externalsecrets.external-secrets.io. The controller pod should show Running with all containers ready.
Configuring Your First SecretStore
Below are complete, working examples for the most common providers. Each example assumes you have already set up the necessary cloud provider authentication (IRSA for AWS, Workload Identity for GCP, or pod identity for Azure).
AWS Secrets Manager
First, ensure your cluster has IAM permissions to access Secrets Manager. When using IRSA (IAM Roles for Service Accounts), create a service account with the appropriate role and reference it in the SecretStore:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: my-app
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: eso-sa # Service account with IAM role for Secrets Manager access
If you prefer static credentials (not recommended for production), you can use secretRef authentication instead:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager-static
namespace: my-app
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key-id
secretAccessKeySecretRef:
name: aws-credentials
key: secret-access-key
Azure Key Vault
For Azure Key Vault, configure the vault URL and authentication. The example below uses workload identity with a service account:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: azure-keyvault
namespace: my-app
spec:
provider:
azurekv:
vaultUrl: https://my-vault-name.vault.azure.net
authType: WorkloadIdentity
serviceAccountRef:
name: eso-sa # Service account with Azure Workload Identity federation
Google Cloud Secret Manager
For GCP, specify the project ID and use workload identity authentication:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: gcp-secret-manager
namespace: my-app
spec:
provider:
gcpsm:
projectID: my-gcp-project-id
auth:
workloadIdentity:
serviceAccountRef:
name: eso-sa # K8s service account federated with GCP service account
HashiCorp Vault
Connecting to HashiCorp Vault requires the server address and authentication details. Here is an example using token-based authentication:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-store
namespace: my-app
spec:
provider:
vault:
server: https://vault.example.com
path: kv-v2/my-secrets
version: v2
auth:
tokenSecretRef:
name: vault-token
key: token
For Kubernetes-based authentication to Vault (where Vault trusts the Kubernetes cluster's JWT tokens), use:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-store-k8s-auth
namespace: my-app
spec:
provider:
vault:
server: https://vault.example.com
path: kv-v2/my-secrets
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: my-vault-role
serviceAccountRef:
name: eso-sa
Creating ExternalSecret Resources
Once your SecretStore (or ClusterSecretStore) is in place, create an ExternalSecret to fetch secrets. This resource declares which keys to pull and how to map them into the target Kubernetes Secret.
Basic Example: Single Secret Key
The simplest use case fetches a single secret value from the provider and places it into a Kubernetes Secret:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-external-secret
namespace: my-app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: my-k8s-secret
creationPolicy: Owner
data:
- secretKey: db-password
remoteRef:
key: prod/database/password
This creates a Kubernetes Secret named my-k8s-secret in the my-app namespace with a single key db-password whose value comes from the AWS Secrets Manager secret named prod/database/password. The refreshInterval of 1h tells ESO to reconcile every hour.
Fetching Multiple Keys from a JSON Secret
Many providers store secrets as JSON blobs. ESO can parse JSON and extract individual properties into separate Kubernetes Secret keys:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: my-app
spec:
refreshInterval: 5m
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: db-creds
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/database/credentials
property: username
- secretKey: password
remoteRef:
key: prod/database/credentials
property: password
- secretKey: host
remoteRef:
key: prod/database/credentials
property: host
- secretKey: port
remoteRef:
key: prod/database/credentials
property: port
Here, the AWS secret prod/database/credentials contains a JSON object like {"username":"admin","password":"s3cret!","host":"db.example.com","port":"5432"}. ESO extracts each property into its own key in the resulting Kubernetes Secret.
Using Metadata and Version Information
You can also pull metadata alongside secret values. For AWS Secrets Manager, for example, you can retrieve the secret version ID:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: secret-with-metadata
namespace: my-app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: my-secret-with-version
creationPolicy: Owner
data:
- secretKey: api-key
remoteRef:
key: prod/api/key
- secretKey: version-id
remoteRef:
key: prod/api/key
version: latest
metadata: versionId
Working with ClusterSecretStore
When multiple teams across different namespaces need access to the same external secret provider, a ClusterSecretStore avoids duplicating provider configuration. Platform teams create it once, and developers reference it from any namespace.
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: global-aws-secrets
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: eso-sa
namespace: external-secrets # Service account in the ESO namespace
Now any ExternalSecret in any namespace can use this store:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: team-a-secret
namespace: team-a
spec:
refreshInterval: 30m
secretStoreRef:
name: global-aws-secrets
kind: ClusterSecretStore
target:
name: app-secret
creationPolicy: Owner
data:
- secretKey: token
remoteRef:
key: team-a/service-token
This pattern centralizes credential management while keeping secrets namespace-scoped in their target locations.
Advanced Features
Secret Templating and Transformation
ESO supports Go templates for transforming secret values before they land in the Kubernetes Secret. This is useful when you need to combine multiple values or format them in a specific way:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: templated-secret
namespace: my-app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: connection-string
creationPolicy: Owner
template:
engineVersion: v2
data:
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@{{ .host }}:{{ .port }}/mydb"
data:
- secretKey: username
remoteRef:
key: prod/database/credentials
property: username
- secretKey: password
remoteRef:
key: prod/database/credentials
property: password
- secretKey: host
remoteRef:
key: prod/database/credentials
property: host
- secretKey: port
remoteRef:
key: prod/database/credentials
property: port
The resulting Kubernetes Secret will contain a single key DATABASE_URL with the fully formed connection string. The template variables {{ .username }}, {{ .password }}, etc., are populated from the data blocks defined in the ExternalSecret.
Controlling Deletion Behavior
The creationPolicy field controls what happens when the ExternalSecret is deleted:
Owner— The KubernetesSecretis deleted when theExternalSecretis deleted.Orphan— The KubernetesSecretremains even after theExternalSecretis removed.Merge— ESO merges fetched keys into an existingSecretwithout taking ownership.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: orphan-secret
namespace: my-app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: persistent-secret
creationPolicy: Orphan # Secret survives ExternalSecret deletion
data:
- secretKey: api-key
remoteRef:
key: prod/api/key
Automatic Secret Rotation Detection
ESO can detect secret rotation events from providers that support event-driven notifications (such as AWS Secrets Manager rotation). Combine refresh intervals with rotation detection for near-real-time updates:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: auto-rotated-secret
namespace: my-app
spec:
refreshInterval: 1h # Periodic reconciliation as a fallback
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: rotated-creds
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: prod/database/password-rotating
For event-driven refresh, configure the ESO controller to listen for provider events (e.g., AWS EventBridge notifications via SQS). The controller can then reconcile specific ExternalSecret resources immediately when the backing secret changes, rather than waiting for the next refreshInterval window.
Specifying Secret Versions
For providers that support versioning (AWS Secrets Manager, GCP Secret Manager), you can pin to a specific version or always fetch the latest:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: pinned-version-secret
namespace: my-app
spec:
refreshInterval: 24h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: stable-creds
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: prod/database/password
version: "a1b2c3d4-5678-90ab-cdef-EXAMPLE11111" # Pin to a specific version
Best Practices
Scope SecretStores Appropriately
Use namespace-scoped SecretStore resources for team-specific providers and ClusterSecretStore for shared platform-wide providers. Avoid giving every namespace access to every provider—limit blast radius by scoping store references to the namespaces that genuinely need them.
Apply Least Privilege to Service Accounts
The service account referenced in a SecretStore should have permissions only for the specific secrets that namespace requires. For AWS, use resource-level permissions in IAM policies; for Vault, use path-scoped ACL policies; for Azure, limit the Key Vault access policy to specific secrets.
Set Appropriate Refresh Intervals
Choose refresh intervals based on your secret rotation cadence and the cost of provider API calls. Short intervals (1–5 minutes) are appropriate for rapidly rotating secrets in high-security environments. Longer intervals (1–24 hours) work well for stable credentials. Balance freshness against provider rate limits and costs.
Monitor ExternalSecret Status Conditions
Each ExternalSecret exposes status conditions that indicate whether reconciliation succeeded. Monitor these conditions with your observability stack:
# Check the status of an ExternalSecret
kubectl get externalsecret my-external-secret -n my-app -o jsonpath='{.status.conditions}' | jq .
Common conditions include Ready, FetchSucceeded, and Synced. Alert on any condition that stays False for an extended period.
Use Template Functions for Complex Transformations
When secrets require formatting beyond simple key-value mapping (connection strings, configuration files, combined credentials), leverage ESO's template engine. Keep templates readable and version them alongside your infrastructure-as-code to track changes.
Implement Deletion Policies Intentionally
Choose creationPolicy values deliberately. Owner is safest for ephemeral secrets managed entirely by ESO. Orphan prevents accidental data loss during ESO maintenance but requires manual cleanup. Merge is useful when ESO supplements an existing Secret with additional keys from an external provider.
Secure the SecretStore Credentials
The credentials used to authenticate to external providers (access keys, tokens, service principal secrets) should themselves be stored securely. Use Kubernetes Secrets referenced via secretRef or, better yet, avoid static credentials entirely by using workload identity federation (IRSA, Azure Workload Identity, GCP Workload Identity Federation). Never embed raw credentials in SecretStore YAML.
Plan for Provider Failures
ESO caches secrets in Kubernetes Secret resources, which means applications continue to function even if the external provider is temporarily unavailable. However, if a pod restarts and the cached Secret has expired keys, failures can occur. Design applications to handle credential failures gracefully, and set refreshInterval conservatively enough that the cache remains valid during typical provider outages.
Audit and Version Your ExternalSecret Definitions
Treat ExternalSecret and SecretStore YAML files as part of your infrastructure-as-code. Store them in version control, review changes through pull requests, and apply them via GitOps workflows (Flux, ArgoCD) rather than ad-hoc kubectl apply commands.
Enable Controller Metrics
ESO exposes Prometheus metrics for reconciliation counts, latency, and error rates. Scrape these metrics to build dashboards and alerts. Key metrics include external_secrets_sync_calls_total, external_secrets_sync_duration_seconds, and external_secrets_store_ready.
# Example Prometheus ServiceMonitor for ESO
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: external-secrets-monitor
namespace: external-secrets
spec:
selector:
matchLabels:
app.kubernetes.io/name: external-secrets
endpoints:
- port: metrics
interval: 30s
Conclusion
External Secrets Operator fundamentally improves how Kubernetes workloads consume sensitive data. By bridging external secret stores with native Kubernetes Secret resources, ESO eliminates the security gaps inherent in storing secrets directly in cluster etcd or injecting them via CI/CD pipelines. It centralizes secret management, enforces a single source of truth, enables automatic rotation, and provides comprehensive audit trails through the underlying provider's logging capabilities.
The implementation pattern is straightforward: define a SecretStore or ClusterSecretStore to establish provider connectivity, then create ExternalSecret resources that declare which secrets to fetch and how to map them into Kubernetes Secret keys. Advanced features like templating, version pinning, deletion policies, and event-driven refresh give teams precise control over secret lifecycle management. By following best practices—least-privilege service accounts, appropriate refresh intervals, monitoring of status conditions, and infrastructure-as-code management—you can build a robust, auditable, and scalable secret management layer that serves every namespace in your cluster without compromising security or developer experience.