Every platform eventually faces the same awkward question from a tenant: "where do my secrets live?" The honest answer on most young self-hosted platforms is embarrassing — plaintext handed over at deploy time, stuffed into a per-app Kubernetes Secret the platform owns forever, rotated only when someone remembers to redeploy. It works right up until the first enterprise tenant asks to keep their secrets in their own Vault and have your platform read from it. At that point you discover that "we hold your secrets" was never a strategy, just a postponement.
The industry has quietly settled this question while nobody was watching. The External Secrets Operator (ESO) — a CNCF project that syncs secrets from Vault, cloud KMS platforms, and 30-plus other backends into ordinary Kubernetes Secrets — has become the default answer for secrets on Kubernetes in 2026. Adoption passed 45 million downloads in early 2026, the project shipped its v1.0 GA milestone with stable v1 APIs, it sits at the top of every 2026 operators-to-install roundup, and Red Hat now ships it as a supported OpenShift component. The verdict for a small self-hosted PaaS: ESO is the standard sync target your tenant-secrets story should be built around — not because it removes work, but because it moves the "which backend" argument off your plate entirely. Here is the concrete buy-vs-cost case.
The three-way race, settled
Kubernetes secrets management in 2026 has three serious patterns, and they are not really competing anymore — they have stratified by team shape. What gets committed to Git tells you which one you're looking at: ciphertext (SOPS/Sealed Secrets), a reference (ESO), or nothing at all because a sidecar fetches at runtime (Vault agent).
| Approach | What's in Git | Rotation story | Backend flexibility | Operational cost |
|---|---|---|---|---|
| SOPS + age/PGP, Sealed Secrets | Encrypted blobs | Manual: re-encrypt, re-commit, re-apply | None — the cluster is the backend | Near zero: a key, or one tiny controller |
| External Secrets Operator | References ("fetch key X from store Y") | Automatic: controller re-syncs on refreshInterval | 30+ providers: Vault, AWS/GCP/Azure, 1Password, Bitwarden, webhooks | One controller per cluster, plus provider credentials to bootstrap |
| Full Vault (+ agent/CSO driver) | References or nothing | Dynamic short-lived credentials, PKI | Vault is the backend — the deepest option, and the only one | A Vault cluster: unseal, HA, storage, upgrades |
The which-team-picks-what guidance has converged: a small team with few clusters and a low rotation cadence should take Sealed Secrets — simplest, no external dependency. A team already keeping secrets in AWS Secrets Manager or Azure Key Vault should take ESO, which extends storage they already pay for into Kubernetes. A multi-cluster fleet that needs the same secrets consistently everywhere should take ESO, because its reference model isn't cluster-scoped the way re-sealing per cluster is. And Vault earns its keep only when you need what nothing else offers: dynamic credentials, PKI, and audit trails that satisfy a compliance department.
Note the honest shape of that table: SOPS and Sealed Secrets didn't lose. They own a real niche — GitOps-file encryption for teams whose rotation cadence is "rarely" — and the well-worn homelab upgrade path still runs SOPS+age first, ESO-plus-backend later. ESO won the middle, which is where every growing platform lives: too many rotations for manual re-encryption, too small an ops team for Vault.
What tenant-scoped ESO on a fleet concretely looks like
The core mechanic is one Custom Resource. A tenant declares what they need and where it lives upstream; the controller materializes it as a native Secret the workload consumes through the usual envFrom, secretKeyRef, or volume mounts:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: checkout-db-creds
namespace: tenant-acme
spec:
refreshInterval: 15m
secretStoreRef:
name: acme-vault
kind: SecretStore
target:
name: checkout-db-creds
data:
- secretKey: password
remoteRef:
key: prod/checkout/db
property: passwordTwo scoping decisions make this a fleet story rather than a single-cluster trick. First, SecretStore is namespace-scoped while ClusterSecretStore is cluster-scoped — so the platform can offer per-tenant SecretStore objects living in tenant namespaces (each wired to that tenant's own Vault or cloud KMS) alongside a small set of platform-owned ClusterSecretStores for shared backend classes. The tenant's plug-in UX is exactly as thin as it should be: they supply credentials to their backend, the platform supplies the Store CR, and from then on the tenant's secrets never transit the platform's deploy pipeline as plaintext. Second, because what's committed is a reference and not a value, the same ExternalSecret shape works identically on every workload cluster in the fleet — no per-cluster re-sealing, no per-cluster ciphertext.
The Cluster-API rollout shape follows from that: ESO becomes a default fleet add-on, versioned and rolled out like cert-manager or the CNI — one more controller per workload cluster, pinned to the fleet's tested version. For tenants who need secrets pushed the other direction (platform-generated credentials landed into their Vault), ESO's PushSecret covers the reverse flow without a second tool.
Costed against the baseline from the top of this post — platform holds plaintext, rotation means redeploy — the trade is: you accept one more controller per cluster and the job of bootstrapping provider credentials, and in exchange you never again have to build, bless, or defend one specific secrets backend. Any tenant's existing Vault or cloud KMS plugs into the same sync target. For a platform whose pitch is fewer moving parts, that "one more controller" line item stings — but it replaces an unbounded support surface ("do you integrate with our CyberArk? our 1Password? our GCP Secret Manager?") with a bounded one.
What ESO doesn't solve
Adopting the default answer doesn't retire the fundamentals. Four things teams routinely assume ESO handles, which it does not:
Synced values still land as native Secrets. ESO resolves references into ordinary Kubernetes Secrets, which means etcd encryption at rest via EncryptionConfiguration is still mandatory — it is not enabled by default on most clusters — and RBAC still decides who can read them. ESO moves the source of truth out of the cluster; it does not change what lives inside it.
Rotation has a lag. ESO re-syncs on refreshInterval, so a rotated upstream secret reaches the cluster minutes later, and workloads that read secrets only at startup won't see it until they restart. That's a strict improvement over manual re-encryption, but it's polling, not dynamic credentials — if you need sub-minute revocation, that's the Vault row of the table.
Scoping bugs are the sharp edge. The May 2026 CVE-2026-42875 — a namespace-isolation bypass in ESO's CA-provider ConfigMap resolution — is the cautionary tale for exactly the per-tenant scoping this post recommends: the namespace boundary your multi-tenancy depends on is only as good as the controller enforcing it. Keep the fleet add-on pinned to a patched version and treat ESO upgrades as security-sensitive, not cosmetic.
Project health deserves a glance. ESO went through a real maintainer-burnout crisis in 2025 — releases paused mid-year and the CNCF TOC opened a project-health issue — before recovering to the v1.0 GA in November 2025 and the current v2.x stable line. That history argues for pinning the fleet add-on to a tested version and watching the release cadence, not for avoiding the project: every default answer has scar tissue, and ESO's is at least public.
Provider credentials are still your problem. ESO needs credentials to talk to each backend — an AWS IAM role, a Vault token, a GCP service account — and bootstrapping those per tenant per cluster is platform work no CRD does for you. IRSA-style workload identity trims it on clouds that offer it; on owned bare metal, plan for a real credential-distribution story.
The adoption ladder
For a git-push PaaS, the sane entry sequence has three rungs, and most platforms should be honest about which rung they're actually on:
- Native Secrets plus etcd encryption. Where everyone starts. Acceptable while the platform holds few secrets and rotation is rare — but name it as a rung, not a destination, and turn on
EncryptionConfigurationon day one. - ESO at the first BYO-backend request. The moment a tenant asks to keep secrets in their own Vault or KMS — or the moment manual rotation causes the first incident — adopt ESO as a fleet add-on with per-tenant
SecretStores. This is the rung the whole industry converged on, and there is no prize for being the last platform to climb to it. - Vault-backed ESO at enterprise scale. When dynamic short-lived credentials, PKI, or compliance-grade audit trails enter the picture, stand up Vault — and keep ESO as the sync layer in front of it, so tenants see one stable interface regardless of what backs it.
The through-line: each rung keeps the tenant-facing contract stable (declare a reference, get a Secret) while the backing store gets more serious underneath. That stability is the actual product ESO sells to a platform team. The "fewer moving parts" pitch survives intact — you added one controller and deleted an entire category of backend-integration arguments.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



