Kubernetes secrets management: Sealed Secrets, ESO, and Vault compared

Kubernetes Secrets are base64-encoded, not encrypted. For GitOps workflows, that means plain secret values end up in Git or in etcd without protection unless you add a dedicated tool. Sealed Secrets, External Secrets Operator, and HashiCorp Vault each solve this problem differently. This comparison breaks down the architecture, GitOps fit, multi-cluster behavior, and operational cost of each option so you can pick the right one for your team.

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:

  1. Controller (runs in kube-system): generates and manages RSA key pairs, watches for SealedSecret resources, decrypts them, and creates native Kubernetes Secrets.
  2. kubeseal CLI (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 provider
  • ClusterSecretStore (cluster-scoped): same, accessible cluster-wide
  • ExternalSecret: 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.

Recurring server or deployment issues?

I help teams make production reliable with CI/CD, Kubernetes, and cloud—so fixes stick and deploys stop being stressful.

Explore DevOps consultancy

Search this site

Start typing to search, or browse the knowledge base and blog.