Verdict at a glance
| Criterion | Sealed Secrets | External Secrets Operator | Vault |
|---|---|---|---|
| GitOps fit | Excellent (encrypted values in Git) | Excellent (references in Git, values external) | Moderate (secrets not in Git at all) |
| Secret rotation | Manual re-seal | Automatic via refreshInterval |
Automatic, TTL-based |
| Dynamic secrets | No | No (can pull Vault-generated ones) | Yes (database creds, PKI, etc.) |
| Multi-cluster | Poor (sealing key is cluster-specific) | Excellent (one external store, many clusters) | Excellent (centralized Vault, many clusters) |
| Audit trail | Git history + Kubernetes audit log | Cloud provider audit logs (CloudTrail, etc.) | Comprehensive (every access logged) |
| Infrastructure dependency | Controller only | External secret store required | Vault cluster required |
| Complexity | Low | Medium | High |
| Estimated cost (100 nodes) | ~$200/month | ~$530/month | ~$2,400/month |
Short verdict. Sealed Secrets fits small, single-cluster teams that want the simplest possible GitOps workflow. ESO fits cloud-native teams that already have AWS Secrets Manager, GCP Secret Manager, or Azure Key Vault and need multi-cluster rotation. Vault fits enterprises that need dynamic secrets, fine-grained access policies, or a comprehensive audit trail for compliance.
Why base64 is not encryption
Kubernetes Secrets are stored unencrypted by default in etcd. The data field in a Secret manifest uses base64 encoding, which is a serialization format, not a security measure. Reversing it takes one command:
echo "cGFzc3dvcmQxMjM=" | base64 -d
# Output: password123
Anyone with API access to the cluster can retrieve Secrets. Anyone with access to etcd (directly or through backups) can read them in full. The official Kubernetes documentation states: "Anyone with API access can retrieve or modify a Secret, and so can anyone with access to etcd."
This is the baseline problem. All three tools in this comparison exist because of it.
etcd encryption at rest
Kubernetes supports encryption at rest via an EncryptionConfiguration resource passed to the API server. The recommended approach for production is KMS envelope encryption: a unique data encryption key (DEK) per resource, wrapped by a key encryption key (KEK) from an external KMS like AWS KMS, GCP Cloud KMS, or Azure Key Vault.
Managed providers (EKS, GKE, AKS) may enable envelope encryption by default depending on cluster configuration, but do not assume it is active without verifying.
etcd encryption at rest is a separate concern from the tools compared below. Sealed Secrets, ESO, and the Vault Secrets Operator all create native Kubernetes Secrets that land in etcd. Only the Vault Agent Injector and the CSI Driver avoid etcd entirely by writing secrets to memory-backed volumes.
Sealed Secrets
Sealed Secrets takes the encrypt-and-commit approach. You encrypt a Secret locally using the controller's public key, commit the encrypted SealedSecret to Git, and the in-cluster controller decrypts it.
How it works
Two components:
- Controller (runs in
kube-system): generates and manages RSA key pairs, watches forSealedSecretresources, decrypts them, and creates native Kubernetes Secrets. kubesealCLI (your workstation): encrypts a plain Secret manifest using the controller's public certificate.
The encryption is hybrid: AES-256-GCM for the secret value, with the session key wrapped using RSA-2048. The encrypted SealedSecret is safe to commit to a public repository.
GitOps workflow
# 1. Create a plain Secret (do not apply it)
kubectl create secret generic db-creds \
--from-literal=password=s3cretV4lue \
--dry-run=client -o yaml > secret.yaml
# 2. Seal it
kubeseal < secret.yaml > sealed-secret.yaml
# 3. Delete the plain Secret
rm secret.yaml
# 4. Commit sealed-secret.yaml to your Git repository
Argo CD and Flux both natively sync SealedSecret resources. The controller handles decryption in-cluster. If you have an Argo CD-based GitOps setup, SealedSecret manifests work like any other Kubernetes resource in your Application.
Scoping
| Scope | Bound to | When to use |
|---|---|---|
| Strict (default) | Name + namespace | Highest security; prevents cross-namespace replay |
| Namespace-wide | Namespace only | Allows renaming within the same namespace |
| Cluster-wide | Neither | Use sparingly; increases blast radius if key leaks |
Strict scope embeds the name and namespace into the encryption input, making a SealedSecret unusable in a different namespace even if the private key is the same.
Key rotation
Sealing keys are automatically renewed every 30 days. New keys are appended; old keys are retained for decryption. This means key rotation does not require re-sealing existing SealedSecrets, but it also means the secret values themselves are not rotated. Changing the password inside a sealed secret still requires a manual re-seal.
The multi-cluster problem
Each controller generates its own RSA key pair. A SealedSecret sealed for cluster A cannot be decrypted by cluster B. For fleet-wide GitOps, every cluster needs its own sealed version of every secret, or you share a single private key across clusters (which eliminates cluster-level isolation).
Disaster recovery
If the sealing private key is lost, all existing SealedSecrets become permanently inaccessible. Back up the key:
kubectl get secret -n kube-system \
-l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > sealing-keys.yaml
Since keys are renewed every 30 days, repeat this backup at least monthly.
Weakness: what Sealed Secrets does not do
- No dynamic secret generation
- No automatic secret value rotation
- Multi-cluster management is operationally heavy
- The private key is a single point of failure
- No fine-grained access auditing beyond Kubernetes audit logs
External Secrets Operator
External Secrets Operator (ESO) takes the reference-and-fetch approach. You commit an ExternalSecret manifest (containing only a reference, never a value) to Git. The operator pulls the actual value from an external secret store at runtime.
How it works
ESO extends Kubernetes with three CRDs:
SecretStore(namespace-scoped): defines the connection to an external providerClusterSecretStore(cluster-scoped): same, accessible cluster-wideExternalSecret: specifies which external secret to fetch and how to map it to a Kubernetes Secret
On a configurable schedule, the operator connects to the external store, fetches values, and creates or updates a Kubernetes Secret.
Supported providers
ESO supports 40+ provider backends. Stable providers include AWS Secrets Manager, AWS Parameter Store (SSM), GCP Secret Manager, Azure Key Vault, HashiCorp Vault, Oracle Vault, Akeyless, and CyberArk. Beta and alpha providers include 1Password, Bitwarden, Doppler, GitLab Variables, and many more.
Vault is one of many options, not a requirement.
GitOps workflow
# Committed to Git: tells ESO where to find the secret, not the value itself
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-creds
namespace: production
spec:
refreshInterval: 15m # poll every 15 minutes
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-creds # the Kubernetes Secret ESO creates
data:
- secretKey: password # key in the K8s Secret
remoteRef:
key: production/db-creds # path in AWS Secrets Manager
property: password
Zero secret values in Git. Only references and metadata.
Refresh interval and rotation
The refreshInterval field controls how often ESO polls the external provider. The default is zero, meaning secrets are fetched once and never updated. Set it explicitly (e.g., refreshInterval: 15m) if you need automatic rotation.
When an update is detected, ESO updates the Kubernetes Secret. It does not automatically trigger pod restarts; you need a tool like Reloader or a deployment rollout strategy for that.
Multi-cluster strength
A ClusterSecretStore pointing to a single AWS Secrets Manager account (or any supported provider) serves any number of clusters. No per-cluster re-encryption, no per-cluster key management. This is where ESO has a clear structural advantage over Sealed Secrets.
Weakness: etcd exposure
ESO creates native Kubernetes Secrets. Those secrets land in etcd. If you use ESO, etcd encryption at rest is strongly recommended.
Vault on Kubernetes
HashiCorp Vault is a full secrets management platform. It stores secrets, generates dynamic credentials, enforces fine-grained access policies, and logs every access event. On Kubernetes, Vault offers three integration methods:
Vault Secrets Operator (VSO)
The modern, Kubernetes-native approach. VSO uses CRDs (VaultStaticSecret, VaultDynamicSecret, VaultPKISecret) to synchronize Vault secrets into Kubernetes Secrets. One controller per cluster. Lowest resource consumption. Secrets are independent of pod lifecycle.
Limitation: currently supports only the Kubernetes auth method.
Vault Agent Injector
A mutating webhook that injects a vault-agent sidecar into annotated pods. The sidecar authenticates to Vault, fetches secrets, and writes them to an in-memory volume (tmpfs). Secrets never reach etcd. Supports all Vault auth methods and Golang templating.
Trade-off: creates per-pod connections to Vault, so at scale the load on Vault is the highest of the three methods.
Vault CSI Provider
Uses the Container Storage Interface to mount secrets as ephemeral volumes. Vendor-neutral (works alongside other CSI-compatible stores). Secrets stay out of etcd. Requires a DaemonSet with privileged access.
Integration method comparison
| Factor | VSO | Agent Injector | CSI Provider |
|---|---|---|---|
| Secrets in etcd | Yes | No | No |
| Vault load | Lowest | Highest | Moderate |
| Auth methods | Kubernetes only | All | Kubernetes, JWT |
| Secret templating | No | Yes (Golang) | No |
| Pod lifecycle coupling | Independent | Coupled | Coupled |
| Privileged pod required | No | No | Yes |
Dynamic secrets: Vault's unique differentiator
Neither Sealed Secrets nor ESO can generate secrets. Vault can. Through its database secrets engine, Vault creates short-lived, on-demand database credentials with a TTL. When the lease expires, Vault revokes the credentials and deletes the database user. No password sharing, no stale credentials, automatic expiry.
The "secret zero" problem
Every secrets tool needs an initial credential to authenticate. Vault addresses this through the Kubernetes auth method: a pod presents its ServiceAccount JWT to Vault, Vault validates it against the Kubernetes API server via TokenReview, and issues a scoped Vault token. No pre-shared secrets needed. The Kubernetes identity system itself is the trust anchor. The JWT in this exchange is the projected service account token that the kubelet rotates automatically; Vault re-validates it on every renewal, so a leaked token has at most an hour of usefulness before it is invalidated by rotation.
If you have RBAC configured with least-privilege service accounts, the same ServiceAccount identity that controls API access also controls Vault access.
Weakness: operational cost
Vault is the most complex option. A production HA deployment requires 3-5 dedicated StatefulSet pods, operational expertise for unsealing, upgrades, and backups, and ongoing monitoring. HCP Vault (the managed service) shifts operational burden to SaaS pricing, but the conceptual complexity remains.
When to choose each
Choose Sealed Secrets when
- You run a single cluster or a very small fleet
- No existing external secret store infrastructure
- Secrets change infrequently
- You want the simplest possible GitOps-first workflow
- Your team is small and prefers self-contained tooling
Choose ESO when
- You already use AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, or another supported provider
- Multi-cluster environments where secrets should be centrally managed
- You need automatic rotation (set
refreshInterval) - You prefer zero secret values in Git (only references)
- Cloud provider audit logs (CloudTrail, Cloud Audit Logs) are part of your compliance story
Choose Vault when
- Dynamic secret generation is required (database credentials, PKI certificates)
- Enterprise compliance demands comprehensive, per-access audit trails
- On-premises or hybrid environments without a managed cloud secret store
- Fine-grained, per-application access policies are a requirement
- Your team has Vault operational expertise (or you use HCP Vault)
The hybrid option: ESO + Vault
Many production setups use ESO as the Kubernetes integration layer with Vault as the backend. ESO's SecretStore points to Vault; ExternalSecret CRDs reference Vault paths. This gives you GitOps-friendly declarative manifests, Vault's dynamic secrets engine, and ESO's multi-cluster synchronization in a single architecture. A production case study across 17 clusters reported zero secrets in Git and automatic propagation without manual intervention for routine rotations.
What this comparison does not cover
SOPS (Secrets OPerationS) is a file-level encryption tool that supports AWS KMS, GCP KMS, Azure Key Vault, and PGP. Flux has native SOPS integration. It is a viable GitOps secrets option but follows a different pattern (encrypt files, not Kubernetes resources) and warrants its own article.
The Secrets Store CSI Driver (the Kubernetes-SIG project, distinct from Vault's CSI Provider) mounts secrets from external stores without creating Kubernetes Secrets. It avoids etcd entirely but has different trade-offs around refresh control and GitOps suitability.