KUBERNETES SECRETS MANAGEMENT IN 2026: ESO, SEALED SECRETS, SOPS, AND VAULT
If you are running Kubernetes in production, you have probably stared at a YAML file containing a literal database password and thought “there has to be a better way.” There is, but the number of options keeps growing and the advice you get depends entirely on who you ask. I have been through this migration myself across several clusters, and the reality is that each approach solves a different problem.
Secrets management in Kubernetes is not a solved problem — it is a series of trade-offs. The tool that works for a three-person startup on a single EKS cluster is going to be different from what a fintech running Vault across five regions needs. The proliferation of options (External Secrets Operator, Sealed Secrets, SOPS, Vault CSI, Vault Agent Injector, and the dozen other niche tools) reflects the diversity of those requirements rather than any failure to converge.
Who Is This Guide For?
You operate Kubernetes clusters, manage CI/CD pipelines, or make architectural decisions about secret infrastructure. You already understand the basics of Kubernetes Secrets but need a clear-eyed comparison of the production-grade options available in 2026. If you are evaluating which approach to standardise on, or wondering whether you should switch from one tool to another, this guide covers the decision space.
By the End of This, You Will Know
- The five major approaches to Kubernetes secrets management and their core trade-offs
- Which tools fit GitOps workflows, which fit dynamic secret environments, and which fit both
- How to pick the right approach based on your team size, compliance requirements, and operational maturity
- A working quick-start for each major option
The State of Kubernetes Secrets in 2026
The Kubernetes Secrets API (kind: Secret) stores data as base64-encoded values in etcd. That is not encryption — it is obfuscation. The API docs have been telling you this since 2016, but I still see production clusters where developers assume kubectl get secret output is somehow protected. It is not.
Three things changed the landscape in the last two years:
External Secrets Operator (CNCF Sandbox, accepted July 2022) became the default choice for teams already using cloud secret managers. It now has over 40 supported providers and is installed in roughly a third of production clusters that report their stack.
SOPS was donated to the CNCF as a sandbox project and adopted
ageas a first-class encryption backend. Its integration with Flux and ArgoCD made it the de facto standard for file-level secret encryption in GitOps pipelines.Vault Secrets Store CSI Driver (v2.x) matured alongside the Agent Injector, offering a sidecar-free path for mounting secrets. HashiCorp’s Injector vs CSI comparison outlines the differences — notably, CSI only supports Kubernetes auth, while Injector supports all auto-auth methods including lease renewal. The choice depends on your use case.
This is the landscape in 2026. Each tool has a clear use case, and the mistake I see most often is teams picking the wrong one because it happened to be the first they heard about.
Approach 1: External Secrets Operator
ESO synchronises secrets from external providers into standard Kubernetes Secret objects. You define an ExternalSecret resource, and the operator creates or updates a corresponding Secret in your namespace. The secret ends up in etcd, but it is fetched from your provider on each reconciliation cycle.
When it shines: You already use AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, or HashiCorp Vault and want a straightforward bridge into Kubernetes. ESO supports over 40 providers including CyberArk, Akeyless, and Pulumi ESC. It is the easiest path if you want a centralised secret store outside Kubernetes without running your own infrastructure.
When it stumbles: Secrets still live in etcd. If your threat model requires that secrets never touch the Kubernetes datastore, ESO is not the right answer. It also means anyone with etcd access (or a ClusterRole that can read secrets across namespaces) can access your synced data.
Installation:
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
-n external-secrets --create-namespace
Minimal example:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
spec:
provider:
aws:
service: SecretsManager
region: eu-west-2
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-creds
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: SecretStore
target:
name: app-secrets
data:
- secretKey: DB_PASSWORD
remoteRef:
key: production/app/db
property: password
ESO watches the remote secret and updates the local Kubernetes Secret when the remote value changes. This makes rotation transparent — rotate the secret in your provider, and the pod picks it up on its next mount or after a rollout restart.
Approach 2: Sealed Secrets
Sealed Secrets takes the opposite approach from ESO. Instead of fetching from an external store, you encrypt your secrets client-side into a SealedSecret custom resource that can be safely committed to git. Only the controller running in your cluster can decrypt it.
The workflow is dead simple:
# Create a local Secret manifest
kubectl create secret generic app-creds \
--from-literal=api_key=sk-abc123 \
--dry-run=client -o yaml > secret.yaml
# Encrypt it with kubeseal
kubeseal -f secret.yaml -w sealed-secret.yaml
# This file is now safe to commit to git
git add sealed-secret.yaml
The controller is installed via Helm:
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets -n kube-system \
--set-string fullnameOverride=sealed-secrets-controller \
sealed-secrets/sealed-secrets
When it shines: You practice GitOps. Sealed Secrets is purpose-built for storing encrypted secrets alongside your application manifests. It supports three scopes (strict, namespace-wide, cluster-wide) that control whether a sealed secret can be renamed or moved between namespaces — a thoughtful design for teams that want fine-grained security.
When it stumbles: Sealed Secrets are bound to a specific cluster’s encryption key. You cannot decrypt a SealedSecret offline without the controller’s private key, which makes disaster recovery more involved. You need to back up the sealing key or use the --re-encrypt workflow if you lose the cluster. The controller manages key rotation automatically (every 30 days by default), but old keys are retained so existing sealed secrets remain functional. Currently at 9.1k GitHub stars with over 1,600 commits, it is one of the most battle-tested options in this space.
Approach 3: SOPS (Secrets OPerationS)
SOPS is a file-level encryption tool that encrypts the values inside YAML, JSON, ENV, and INI files while preserving the structure. It supports AWS KMS, GCP KMS, Azure Key Vault, age, and PGP as encryption backends. Originally created at Mozilla in 2015, it became a CNCF sandbox project in 2023 and is now at v3.13.1 (May 2026) with 21.9k GitHub stars.
The killer workflow is decrypting secrets at deploy time in CI/CD:
# .sops.yaml
creation_rules:
- path_regex: secrets/*.yaml
age: age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw
Encrypt a file:
sops encrypt --age age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw \
secrets/prod.yaml > secrets/prod.enc.yaml
In your deployment pipeline:
# Decrypt and apply in one step
sops decrypt secrets/prod.enc.yaml | kubectl apply -f -
Flux supports this natively with its SOPS integration. ArgoCD can achieve the same via a pre-sync hook or a custom plugin.
When it shines: Multi-cluster, multi-team GitOps. SOPS files are portable — you can decrypt them on any machine with the right key. This makes it the natural choice when you have multiple clusters, or when developers need to decrypt secrets locally without cluster access. The age backend eliminates the complexity of PGP key management while providing strong X25519 encryption.
When it stumbles: SOPS does not handle dynamic secrets. It encrypts values at rest in your repository. If a secret needs to rotate hourly, SOPS is not the tool for that. It also requires your CI/CD pipeline to have the decryption key available, which is a separate security concern you need to manage.
Approach 4: Vault Secrets Store CSI Driver
If your threat model requires that secrets never touch the Kubernetes datastore, the Vault Secrets Store CSI Driver is the right answer. Secrets are mounted into pods as ephemeral CSI volumes — they exist only in the pod’s memory and the Vault server. Nothing is written to etcd.
The architecture involves a SecretProviderClass that defines which Vault secrets to retrieve:
apiVersion: secrets-store.csi.x-k8s.io/v1alpha1
kind: SecretProviderClass
metadata:
name: vault-db-creds
spec:
provider: vault
parameters:
roleName: app
objects: |
- objectName: "db_password"
secretPath: "database/creds/db-app"
secretKey: "password"
And a pod that mounts it:
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: vault-db-creds
When it shines: Zero-trust environments, regulatory compliance, and dynamic secrets. Vault supports short-lived credentials with automatic lease renewal. The CSI driver authenticates using the pod’s service account (via Vault’s Kubernetes auth method), which means no static tokens or long-lived credentials anywhere in the system.
When it stumbles: You need to run Vault. That is a significant operational investment — Vault Enterprise uses custom pricing (contact IBM HashiCorp sales), and self-managing Vault Community requires dedicated expertise for HA configurations, seal/unseal workflows, and DR. If you are not already running Vault, the operational overhead rarely justifies the security gain over ESO or Sealed Secrets.
Vault also offers the Agent Injector as an alternative that injects secrets via a sidecar container. For new deployments in 2026, the CSI path is the recommended approach — it avoids the sidecar overhead and integrates more cleanly with the Kubernetes volume lifecycle.
Approach 5: Native Kubernetes Secrets (With Encryption-at-Rest)
Before adopting any of these tools, you should at minimum enable encryption-at-rest for etcd. This is not a replacement for the tools above, but a baseline you should have regardless of which approach you choose:
# kube-apiserver encryption config
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
providers:
- kms:
name: myKMS
endpoint: unix:///var/run/kmsplugin/socket.sock
- aescbc:
keys:
- name: key1
secret: <base64-encoded-key>
- identity: {}
This ensures that secrets stored in etcd are encrypted at rest. It does not change how secrets are transmitted or who can read them via the API server — RBAC is still your primary access control layer.
Decision Framework
Teams can successfully use all of these approaches. The choice comes down to three factors:
| Scenario | Recommendation | Rationale |
|---|---|---|
| GitOps with Flux/ArgoCD, single cluster | Sealed Secrets | Encrypted in git, cluster-bound, minimal moving parts |
| GitOps with multi-cluster or multi-team | SOPS + age | Portable encrypted files, works with any GitOps tool |
| Already using cloud secrets manager | External Secrets Operator | Direct sync, zero new infra, 40+ providers |
| Need dynamic/short-lived secrets | Vault CSI Driver | Mount without etcd, automatic lease renewal |
| Small team, want simplicity | External Secrets Operator | One Helm install, familiar cloud provider integration |
| Compliance requires no secrets in etcd | Vault CSI Driver | Ephemeral volumes, nothing persists in the datastore |
Quick Comparison
External Secrets Operator syncs secrets from external providers into Kubernetes Secrets stored in etcd. It is the easiest to set up if you already use cloud secret managers. It does not solve the “secrets in etcd” problem, but it eliminates the “secrets in git” problem entirely.
Sealed Secrets encrypts secrets for a specific cluster using asymmetric cryptography managed entirely within Kubernetes. You never need an external provider. The sealed secrets are safe in public repositories. The trade-off is cluster lock-in — you cannot decrypt a sealed secret without that cluster’s controller.
SOPS encrypts files with cloud KMS or age keys. The encrypted files are portable across any cluster, any CI/CD system, any machine with the right decryption key. It is the most flexible option but requires you to manage key distribution for your pipelines.
Vault CSI Driver is the only option that keeps secrets out of etcd entirely. It requires running Vault, which is a significant operational commitment. For organisations that already run Vault or have strict compliance requirements, it is the gold standard.
Implementation Quick Start
I am going to give you a working path for each tool. These are not exhaustive tutorials — they get you from zero to a running example.
External Secrets Operator:
helm install external-secrets external-secrets/external-secrets \
-n external-secrets --create-namespace
# Create a SecretStore pointing at your provider
# Create ExternalSecret resources referencing it
Sealed Secrets:
helm install sealed-secrets -n kube-system \
sealed-secrets/sealed-secrets
# Install kubeseal client
brew install kubeseal # macOS
# Or download from GitHub releases for Linux
# Encrypt a secret
kubeseal --fetch-cert > mycert.pem
kubeseal --cert mycert.pem -f secret.yaml -w sealed.yaml
kubectl apply -f sealed.yaml
SOPS with age:
# Generate an age key
age-keygen -o age.key
# Encrypt
sops encrypt --age $(cat age.key | age-keygen -y 2>/dev/null) \
-i secrets.yaml
# Decrypt
sops decrypt secrets.yaml | kubectl apply -f -
Vault CSI Driver:
Install the Secrets Store CSI Driver and Vault provider:
helm repo add secrets-store-csi-driver \
https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi-secrets-store secrets-store-csi-driver/csi-secrets-store-driver \
-n kube-system
# Install Vault provider via Vault Helm chart
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault -n vault --create-namespace \
--set injector.enabled=false \
--set csi.enabled=true
Then configure a SecretProviderClass and mount it in your pod as shown in Approach 4 above.
Validation
Whichever approach you choose, verify that:
- No secrets exist in plaintext in your git repositories. Run
grep -r "password:" git/ --include "*.yaml"to confirm. - RBAC restricts secret access.
kubectl auth can-i list secrets --as=system:serviceaccount:default:my-appshould returnnofor workloads that do not need it. - Encryption-at-rest is enabled for etcd. Check
/etc/kubernetes/encryption-config.yamlon your control plane nodes. - Audit logging is capturing secret access events. In ESO and Vault, this happens at the provider level. In Sealed Secrets and SOPS, access is controlled by git history and KMS audit logs.
Next Steps
Secrets management is not a one-time decision. As your cluster count grows, your compliance requirements tighten, or your team matures, you will likely migrate between these approaches. I have seen teams start with Sealed Secrets, add ESO when they adopted a cloud secrets manager, and eventually introduce Vault CSI for their most sensitive workloads.
If you already use cloud secret stores, my previous article on Vault vs AWS Secrets vs Azure Key Vault covers the provider comparison in depth. For a broader look at securing Kubernetes workloads, the Kubernetes Pod Security Standards guide is a good follow-up. And if you are running GitOps with Flux or ArgoCD, the Helm vs Kustomize vs ArgoCD comparison covers how secrets fit into the broader deployment picture.