Skip to main content

Sealed Secrets vs External Secrets Operator in 2026: What Git-Stored Ciphertext Really Costs a Three-Person Platform Team Against Vault Sync

9 min readDora NodaDora Noda
Share
On this page

Every multi-tenant platform eventually faces the same uncomfortable question: where do tenant secrets actually live? Not the Kubernetes Secret objects — those are just base64-wrapped plaintext in etcd. The real question is where the source of truth lives, and both settled answers have a bill attached that nobody puts on the landing page.

The decision, up front: if you are a three-person platform team running a multi-tenant fleet on machines you own, start with Sealed Secrets. Its entire cost is key management you can do in an afternoon. External Secrets Operator is the better architecture at scale — automatic rotation, one vault feeding many clusters — but its price of admission is a vault you must run or rent plus a bootstrap credential you must protect, and on owned hardware without a cloud IAM, that admission price exceeds the whole cost of the alternative. Add ESO when your rotation cadence or cluster count says so; the switching thresholds are at the end of this post.

That verdict is for a specific shape of team: tenants who must never read each other's secrets, no hyperscaler IAM to lean on, and headcount too small to staff a secrets service. If that is you, read on for the receipt.

Where the secret actually lives​

The two tools answer "where do secrets live" in opposite ways, and everything downstream — rotation, disaster recovery, tenant isolation — follows from that one choice.

With Sealed Secrets, the secret lives in Git, encrypted. You seal a plaintext Secret with kubeseal against the cluster's public certificate, commit the resulting SealedSecret object, and the in-cluster controller decrypts it with a 4096-bit RSA private key it generated at install time:

yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: tenant-acme-db
  namespace: tenant-acme
spec:
  encryptedData:
    password: AgBy3i4OJSWK+OjJ8v6n... (600+ chars of ciphertext)

With External Secrets Operator, the secret lives in a vault, referenced from Git. What you commit is a pointer — "fetch this key from that store" — and the controller resolves the pointer into a real Secret on a polling interval:

yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: tenant-acme-db
  namespace: tenant-acme
spec:
  refreshInterval: 15m
  secretStoreRef:
    name: tenant-acme-store
    kind: SecretStore
  target:
    name: tenant-acme-db
  data:
    - secretKey: password
      remoteRef:
        key: tenants/acme/db-password

One model has no external dependency and manual rotation. The other has automatic rotation and a mandatory external dependency. There is no third option hiding behind a feature flag — this is the whole trade, and the rest of the post prices it.

The cost table: vault dependency vs key management​

CostSealed Secrets (v0.40.0, Sept 2026)External Secrets Operator (v2.11.0, Sept 2026)
External dependencyNone. Controller + kubeseal CLI only.A secrets manager must exist: self-hosted Vault/OpenBao/Infisical, or rented (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault).
Bootstrap credentialNone. The sealing keypair is generated in-cluster at install.ESO must authenticate to the store. On a cloud that is IRSA/workload identity; on owned Hetzner hardware it is Vault Kubernetes auth or a static token — itself a secret that needs a home.
RotationManual: rotate at the source, re-seal, commit. Every rotation is a GitOps deploy.Automatic: change the value in the vault, the next refreshInterval poll syncs it. No commit, no deploy.
Multi-clusterPainful. Ciphertext is bound to one cluster's key, so each new cluster means re-sealing every secret (or sharing one keypair across clusters, which collapses your blast-radius story).Trivial. Every cluster points at the same vault paths; adding a cluster is a SecretStore manifest, not a re-encryption project.
Tenant isolationCryptographic binding: default strict scope ties ciphertext to one name in one namespace. The boundary is RBAC on who can create SealedSecret objects.Configuration binding: one namespaced SecretStore per tenant pointing at that tenant's vault path. The boundary is vault policy + store scoping.
Worst failure modeLose the private key without a backup and every sealed secret in Git becomes unopenable paper. Key backup is the entire DR plan.Lose the vault and every ExternalSecret in every cluster degrades to its last-synced value, then to nothing on pod reschedule. Vault backup/HA is the entire DR plan.

Read the table as a staffing question. Sealed Secrets asks your team to do one thing reliably: back up a key and guard who can create objects in each namespace. ESO asks your team to operate a stateful security service — HA Raft storage, unseal ceremony, upgrades, backups — or to pay a cloud meter for one, on a fleet whose pitch is owning the machines. One self-hosted operator's public ADR trail ends with them removing Vault and ESO entirely once nothing needed a vault-held credential — the vault was toil without a tenant. That is the honest shape of the dependency: it is never free just because the operator is open source.

Tenant isolation recipes​

"Tenants must never read each other's secrets" is implementable in both models, but the mechanism — and the misconfiguration that breaks it — differs.

Sealed Secrets: strict scope plus RBAC. The default strict scope encrypts the secret's name and namespace into the ciphertext, so a sealed blob for tenant-acme cannot be decrypted into tenant-globex even if someone applies the YAML there — the controller refuses. Your recipe is: keep every tenant secret at strict scope (resist namespace-wide and cluster-wide scopes, which exist for shared platform secrets, not tenant data), and use RBAC so tenant CI can only create SealedSecret objects in its own namespace. The leak to fear is scope creep: one cluster-wide tenant secret, committed for convenience, is readable anywhere the name matches.

ESO: one namespaced store per tenant. Each tenant namespace gets its own SecretStore whose vault role can read only that tenant's path prefix (tenants/acme/*), and tenants get RBAC to create ExternalSecret objects referencing only their store. Avoid ClusterSecretStore for tenant data — it is cluster-scoped by design, and while it supports namespace allowlists, one overly broad allowlist (or one tenant granted use on the wrong store) crosses the streams for every tenant at once. The leak to fear is a shared store with a generous policy: it fails silently, syncing the wrong tenant's value into the wrong namespace with a green SecretSynced status.

Both recipes work. The Sealed Secrets one has fewer moving parts and fails closed (decryption refuses); the ESO one centralizes policy in the vault, which is better once you have enough tenants that per-namespace YAML sprawl becomes its own risk.

Rotation and disaster recovery runbooks​

Rotation is where ESO earns its keep and Sealed Secrets shows its age. With ESO, rotating a database password is a vault write; within one refreshInterval (commonly 5–60 minutes) every referencing cluster converges without a commit or a deploy. With Sealed Secrets, the same rotation is: change the password at the source, re-run kubeseal, commit the new blob, wait for GitOps to sync. For a platform credential rotated quarterly, that is fine. For database passwords on a 30-day policy across dozens of tenants, it is a part-time job — and the most-quoted 2026 practitioner guidance draws exactly this line, recommending Sealed Secrets for small teams with low rotation cadence and ESO once rotation or cluster count grows (SRE secrets-management notes, March 2026; GitOps secrets comparison).

Disaster recovery inverts the burden. Sealed Secrets DR is one artifact: export the controller's private key (kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key), store it offline in two places, and practice kubeseal --recovery-unseal before you need it. The step teams forget is testing the restore — an untested key backup is a rumor. ESO DR is a service recovery: vault snapshots, Raft quorum or cloud replication, unseal keys, and the bootstrap credential ESO itself uses. The step teams forget is the bootstrap credential — you can restore the vault perfectly and still be down because the token ESO logs in with lived only in the old cluster's etcd.

What changed in 2026​

Both projects have had an active year, and the headlines favor "boring and maintained" over "shiny and new."

ESO shipped on a near-monthly cadence through the v2 line, from v2.6.0 in early June to v2.11.0 on September 18, 2026 (Helm chart 2.11.0 followed on September 21), with the steady provider-matrix and polling-hardening work that cadence implies. Sealed Secrets reached v0.40.0 on September 10, 2026, and its two most important 2026 changes are both security hardening: v0.39.0 stopped the /v1/verify endpoint from acting as a decryption oracle and rate-limited /v1/rotate, then v0.40.0 closed the same oracle class on /v1/rotate itself (PR #2019, PR #2049). If you run Sealed Secrets on anything older than v0.39, the upgrade is not optional — an endpoint that answers "does this decrypt" differently for valid and invalid input is a key-recovery aid wearing a feature's clothes.

Adoption-wise both are settled: roughly 9,300 GitHub stars for Sealed Secrets and 6,900 for ESO as of late September 2026, with ESO's star count understating its footprint since it usually arrives bundled inside platform distributions. Neither project is going away; pick on architecture fit, not momentum.

When to switch​

Start sealed. Migrate to ESO plus a self-hosted vault when — and only when — you trip one of these thresholds:

  1. Rotation cadence exceeds your GitOps tolerance. Any secret rotated more often than monthly, or more than a handful of tenants on a 30/90-day password policy, turns re-seal-and-commit from a chore into a queue.
  2. Second cluster. The day tenant secrets must exist identically on two clusters, per-cluster re-sealing becomes a standing tax and ESO's reference model pays for the vault on its own.
  3. Dynamic secrets. If you ever need short-lived database credentials or PKI certificates minted per workload, neither static model suffices — that is Vault-with-ESO (or Vault's own injector) territory, and Sealed Secrets cannot follow you there.
  4. Audit pressure. When someone asks "who read this secret, and when," a vault's audit log answers in one query. Git history answers "who changed it," which is a different question.

Below those lines, the vault is a service you staff for features you do not use. Above any one of them, ciphertext in Git is a ceiling you will keep hitting. The good news is the migration path is one-directional and unsurprising: stand up the vault, point ExternalSecret objects at the same values, delete the sealed blobs. Nothing about starting sealed makes that harder — which is exactly why starting sealed is cheap.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Tenant secrets included: sealed at rest, scoped per namespace. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex