Build Daily

Tinley Park · October 29, 2026
toolThe External Secrets community (CNCF)applied

External Secrets Operator

A Kubernetes operator that copies secrets from an external manager (OpenBao, HashiCorp Vault, AWS Secrets Manager, Google Secret Manager, Azure Key Vault, 1Password and many more) into native Kubernetes Secrets. A SecretStore describes how to reach and log in to the provider, an ExternalSecret names the keys to fetch, and the controller re-reads them on an interval so the Secret follows the source. Apache 2.0 and free. It fits when the source of truth for secrets lives outside the cluster and Git should hold only references.

Updated October 17, 2026

External Secrets Operator (ESO) is a Kubernetes controller that reads secrets from an external secret manager and writes them into ordinary Kubernetes Secrets. The values stay in the manager, and the manifests in Git carry only a reference such as "key airflow/core, all properties". Workloads keep reading a normal Secret and need no client library for the manager.

What it is

ESO is an Apache 2.0 project under the CNCF, written in Go and installed with a Helm chart. It grew from the merge of several earlier "external secrets" controllers, and it now ships providers for a long list of backends: OpenBao and HashiCorp Vault, AWS Secrets Manager and Parameter Store, Google Secret Manager, Azure Key Vault, IBM Cloud, CyberArk Conjur, 1Password, Bitwarden, Infisical, Doppler, Akeyless, GitLab and GitHub variables, a generic webhook, and others.

The current line is v2.13 (v2.13.0 and chart 2.13.0 shipped 2026-10-16). Minor releases arrive every two to three weeks, and the stability and support page states that only the newest minor is supported: each release ends the life of the one before it. Recent 2.x releases are tested against Kubernetes 1.36. Milestones worth knowing:

Release Date Change
v0.16.0 2025-04 external-secrets.io/v1 API added; v1alpha1 and the conversion webhooks removed
v0.17.0 2025-05 v1beta1 no longer served; manifests must use v1
v0.19.2 → v0.20.0 2025-08 → 2025-09 Releases paused for lack of maintainers, then resumed
v1.0.0 2025-11 First 1.x release
v2.0.0 2026-02 Unmaintained Alibaba and Device42 providers removed
v2.7 / v2.10 2026-06 / 2026-08 Dedicated openbao provider added in v2.7; Kubernetes auth for it in v2.10

How it works

The Helm chart installs three Deployments: the controller that reconciles the custom resources, a webhook that validates them, and a cert-controller that issues and rotates the webhook's TLS certificate. Everything else is configuration in custom resources:

  • A SecretStore (namespaced) or ClusterSecretStore (cluster-wide) holds the provider address and the login method.
  • An ExternalSecret points at a store, lists the remote keys, and names the target Secret.
  • The controller logs in to the provider, fetches the keys, renders an optional template, and creates or updates the target Secret. It repeats this every refreshInterval.

The figure follows one sync from manifest to pod, and then a value change made at the source.

How External Secrets syncs and rotates a secret

Step 1 of 5. Argo CD or kubectl applies the ExternalSecret. The controller sees the new object and starts a reconcile.

Shown with OpenBao behind a ClusterSecretStore that uses Kubernetes auth, and Reloader rolling the workload.

The controller never pushes into the pod. Its job ends at the Kubernetes Secret, and the usual Kubernetes rules apply from there: a mounted Secret volume is refreshed by the kubelet after a delay, while environment variables are fixed when the container starts. That is why a restart tool such as Reloader usually sits next to ESO.

ESO also runs the reverse direction. A PushSecret copies a Kubernetes Secret out to a provider, and generators (passwords, ECR and GCR tokens, UUIDs and others) create values that an ExternalSecret can use as a source.

When to use it

Reach for it when:

  • The source of truth for secrets is a manager outside the cluster, and the cluster should hold copies only.
  • Git holds every manifest (GitOps), and secret values must not be in it, encrypted or not.
  • Several clusters need the same secret from one place, each with its own scoped login.
  • Rotation happens at the source (a rotation job, a cloud console, a database engine), and the cluster should follow without a manual kubectl step.
  • Workloads expect plain Kubernetes Secrets and should not take a dependency on a vault SDK.

Skip it when:

  • There is one cluster, a handful of secrets and no external manager. Sealed Secrets or SOPS keep the encrypted value in Git with less moving infrastructure.
  • Policy forbids the value ever landing in etcd. The Secrets Store CSI Driver can mount values straight into the pod as files.
  • The manager offers short-lived, per-pod credentials (database leases, cloud workload identity). An agent sidecar or workload identity hands those to the pod directly, which a copied Secret cannot match.

At a glance

Core concepts

  • SecretStore / ClusterSecretStore — how to reach and authenticate to a provider. Namespaced stores keep credentials inside one namespace; cluster stores serve all of them.
  • ExternalSecret — the mapping from remote keys to one target Secret. data picks single keys; dataFrom.extract pulls every property of a key; dataFrom.find matches keys by name or tag.
  • ClusterExternalSecret — stamps an ExternalSecret into every namespace that matches a selector.
  • refreshPolicy — Periodic (default: re-read on refreshInterval), OnChange (only when the ExternalSecret changes), CreatedOnce (sync once).
  • creationPolicy — Owner (default; the Secret is deleted with the ExternalSecret), Orphan, Merge (into an existing Secret), None.
  • deletionPolicy — what happens when the keys disappear at the provider: Retain (default), Delete or Merge.
  • Templates — Go templates in target.template reshape the data, for example into a connection string or a config file.

Key facts

Item Value
Licence Apache 2.0
Current line v2.13 (2026-10-16)
Support window Newest minor only
API group / version external-secrets.io/v1 (v1beta1 unserved since v0.17)
Components controller, webhook, cert-controller
Install Helm chart external-secrets from charts.external-secrets.io
CRD apply Server-side apply; the CRDs exceed the 256 KB client-side limit

ESO vs the common alternatives

Aspect External Secrets Operator Secrets Store CSI Driver Sealed Secrets
Where the value lives External manager, copied into a Secret External manager, mounted as files Encrypted in Git, decrypted into a Secret
Kubernetes Secret created Yes Optional (sync feature) Yes
Rotation Re-read on interval Optional rotation poll Re-encrypt and commit
Needs an external manager Yes Yes No
Works with env vars Yes Only through the synced Secret Yes

How to integrate

Install the operator with Helm. The chart manages the CRDs by default (installCRDs: true):

helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  -n external-secrets --create-namespace --version 2.13.0

A ClusterSecretStore for OpenBao or Vault with Kubernetes auth. The vault provider talks to OpenBao because the API is compatible; the dedicated openbao provider takes the same shape under spec.provider.openbao and requires a serviceAccountRef or secretRef for Kubernetes auth.

apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: openbao
spec:
  provider:
    vault:
      server: https://openbao.example.com:8200
      path: kv            # KV mount
      version: v2
      auth:
        kubernetes:
          mountPath: kubernetes   # auth mount for this cluster
          role: eso
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets

The matching OpenBao side: a policy that can read only this cluster's subtree, and a Kubernetes auth role bound to the controller's service account.

bao policy write eso-prod - <<'EOF'
path "kv/data/prod/*" { capabilities = ["read"] }
EOF
bao write auth/kubernetes/role/eso \
  bound_service_account_names=external-secrets \
  bound_service_account_namespaces=external-secrets \
  policies=eso-prod ttl=1h

An ExternalSecret that pulls every property of one key into one Secret:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: api-env
  namespace: api
spec:
  refreshInterval: 1h
  secretStoreRef:
    kind: ClusterSecretStore
    name: openbao
  target:
    name: api-env
    creationPolicy: Owner
  dataFrom:
    - extract:
        key: prod/api/api-env

Check the result with kubectl -n api get externalsecret api-env: the STATUS column reads SecretSynced and READY is True. A failed login or a missing key shows in kubectl describe events. To force a re-read before the interval, change an annotation on the ExternalSecret (for example force-sync: "<timestamp>").

In the GL stack

External Secrets Operator v2.13.0 runs on the beta and prod clusters, where the product workloads run. The secret manager is OpenBao on the management cluster.

  • Source of truth. Every app Secret starts in OpenBao's KV v2 engine. The manifests in Git hold ExternalSecret objects with references only, and Argo CD applies them like any other resource. A value changes in OpenBao, never with kubectl, because ESO overwrites the cluster copy on the next hourly refresh.
  • Per-cluster auth. OpenBao has one Kubernetes auth mount per client cluster. Each cluster's ClusterSecretStore logs in through its own mount, and the policy behind it allows reads of that cluster's own subtree only. A login from beta therefore cannot read prod's secrets.
  • Rotation reaches the pods. Stakater Reloader runs on the same two clusters. When ESO updates a Secret, Reloader rolls the Deployments that reference it through their annotation, so a rotated value takes effect without a manual restart.
  • Around it. OpenBao's Raft storage is snapshotted nightly to the backup store, and a secret-age exporter warns about secrets that have not changed for too long.

Gotchas

  • Hand edits do not stick. With the default Periodic policy the controller rewrites the Secret on every refresh. Edit the value at the provider.
  • No restart on change. ESO updates the Secret only. Environment variables stay stale until the pod restarts; pair it with Reloader or roll the Deployment.
  • v1beta1 manifests fail. Since v0.17 only external-secrets.io/v1 is served. Old Helm charts and blog examples still show v1beta1.
  • Server-side apply for CRDs. The CRD bundle passes the 256 KB annotation limit of client-side apply. Use kubectl apply --server-side, or the ServerSideApply=true sync option in Argo CD.
  • Owner means delete. With creationPolicy: Owner, deleting or pruning the ExternalSecret deletes the Secret, and pods that need it fail on their next start. Use Orphan for Secrets that must outlive the mapping.
  • CreatedOnce is tracked in status. The docs note that recreating the ExternalSecret syncs again and overwrites the Secret. Add target.immutable: true if the value must never change.
  • Deleted keys linger. The default deletionPolicy: Retain keeps the last copy when the key is removed at the provider. Set Delete where stale credentials are a risk.
  • Upgrade on a short clock. Only the newest minor is supported, and a new minor ships every few weeks. Read the release notes for removed providers and chart changes before each bump.
  • Leader election is off by default. The chart sets leaderElect: false. Turn it on before raising replicaCount above 1; since v2.12 the chart warns about this combination.

Risks

  • Project health. ESO paused all releases in August 2025 because too few maintainers were active, and resumed with v0.20 in September 2025. Releases have been steady since, but the project depends on a small group of volunteers and sponsor companies.
  • Privilege concentration. By default the controller can read and write Secrets in every namespace, and its provider login can read every path its policy allows. Scope the provider policy tightly, use scopedNamespace / scopedRBAC in the chart where one namespace is enough, and prefer namespaced SecretStores for multi-tenant clusters.
  • Values still land in etcd. ESO moves secrets out of Git, not out of the cluster. Turn on Secret encryption at rest and limit get secrets RBAC.
  • Low lock-in. Workloads see plain Secrets, so removing ESO means replacing ExternalSecrets with another way of creating the same Secrets. The provider choice is the bigger commitment.

Alternatives

  • Secrets Store CSI Driver — pick it when values should reach the pod as files without a Kubernetes Secret, or with a short-lived mount per pod.
  • Sealed Secrets — pick it for a single cluster with no external manager: the encrypted value lives in Git and a controller decrypts it.
  • SOPS (with Flux or an Argo CD plugin) — pick it when encrypted files in Git are the source of truth and a KMS or age key does the decryption.
  • OpenBao or Vault Agent injector — pick it when pods need dynamic, leased credentials straight from the manager, with no copy in etcd.

Related