Sealed Secrets vs External Secrets Operator vs SOPS
Three GitOps secret patterns for Kubernetes. Sealed Secrets for pure GitOps, ESO for upstream-store sync, SOPS for Helm-heavy workflows. Decision matrix.
Infrastructure engineer with 10+ years building production systems on AWS, GCP,…

Sealed Secrets vs External Secrets Operator vs SOPS: Quick Verdict
Three dominant patterns for GitOps-friendly Kubernetes secrets, each solving a different problem. Sealed Secrets encrypts the secret in your Git repo with a cluster-specific key; the SealedSecret CRD gets decrypted by an in-cluster controller into a native K8s Secret. Pure GitOps with no external store. External Secrets Operator (ESO) doesn't store secrets at all — it syncs them from upstream stores (Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, Doppler) into K8s Secrets on a polling interval. Source of truth lives elsewhere. SOPS encrypts files at rest; Helm or Kustomize decrypts them at apply time. The honest picks: Sealed Secrets for pure GitOps with no external store, ESO when you already have a cloud secret manager as source of truth, SOPS for Helm-heavy workflows where the encryption-at-the-file-level model fits.
| Tool | Architecture | Source of truth | Rotation story | Best for |
|---|---|---|---|---|
| Sealed Secrets | K8s controller + CRD | Git (encrypted) | Manual re-seal + commit | Pure GitOps, no external store |
| External Secrets Operator | K8s controller polling upstream | Upstream store (Vault, AWS, GCP, etc.) | Automatic via upstream rotation | Existing secret manager in place |
| SOPS | File-level encryption | Git (encrypted), KMS for keys | Manual re-encrypt + commit | Helm / Kustomize / Terraform-heavy stacks |
Last updated: April 2026 — verified against Sealed Secrets 0.27, ESO 0.10.x, SOPS 3.10. Integration paths with ArgoCD, Flux, Helm, and Kustomize tested.
Sealed Secrets: GitOps Without an External Store
Sealed Secrets (originally from Bitnami) is the simplest of the three. The flow:
- You install the Sealed Secrets controller in your cluster — it generates a public/private keypair, the public key is exposed.
- You write a Secret manifest, then run
kubesealagainst it with the public key. Output: aSealedSecretcustom resource with encrypted ciphertext. - You commit the SealedSecret to Git. Anyone can read it; only the cluster's controller (with the private key) can decrypt.
- When the controller sees a SealedSecret in the cluster, it decrypts it into a native K8s Secret, which pods consume normally.
# Encrypt a secret for the cluster
kubectl create secret generic db-creds \
--from-literal=password='supersecret' \
--dry-run=client -o yaml | \
kubeseal -o yaml > db-creds-sealed.yaml
# Commit the sealed file to Git
git add db-creds-sealed.yaml
git commit -m "Add db creds for production"
Strengths
- Pure GitOps: secrets live in Git like everything else, no external runtime service to operate
- Cluster-specific encryption: leaked sealed secrets can't be decrypted by a different cluster
- Tiny operational footprint: a single controller pod, no external dependencies
Weaknesses
- Rotation is manual: changing a secret means re-encrypting locally and pushing a new commit
- Cluster key rotation is painful: if you re-key the controller, every existing SealedSecret needs to be re-sealed against the new key
- No multi-cluster sharing: a SealedSecret encrypted for cluster A can't be decrypted by cluster B (this is by design but limits patterns)
- No audit trail beyond Git history: which application accessed which secret? Git doesn't tell you that
External Secrets Operator: Sync from Upstream Stores
ESO inverts the model: secrets don't live in your repo at all. The source of truth is an external store (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, Doppler, Akeyless, IBM Secrets Manager). ESO runs as a controller that polls the upstream store on an interval and syncs values into native K8s Secrets.
# Upstream provider config
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-store
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
# Pull a specific secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-creds
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-store
kind: SecretStore
target:
name: db-creds
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: production/db
property: password
Strengths
- Single source of truth: you already have AWS Secrets Manager / Vault / etc. — ESO bridges into K8s without duplication
- Automatic rotation propagation: rotate at the upstream store, ESO syncs to K8s within the refresh interval (default 1h)
- Multi-cluster fan-out: same upstream secret syncs to many clusters; sealed secrets can't do this
- Real audit trail: upstream store logs every read; you know which cluster pulled which secret
Weaknesses
- Adds operational complexity: you're now operating ESO + the upstream store + auth between them
- Refresh interval lag: secret rotated upstream takes up to
refreshIntervalto propagate. Default 1h; tune lower for faster propagation, higher for less load - Auth between cluster and store: IRSA on EKS, Workload Identity on GKE, AppRole for Vault — each integration has its own gotchas
- Not pure GitOps: the actual secret value lives outside Git; only the ExternalSecret manifest pointing at it is in Git
The Vault vs AWS Secrets Manager comparison covers picking the upstream store ESO syncs from. The broader alternatives roundup covers all 8 options ESO supports.
SOPS: File-Level Encryption
SOPS (Secrets OPerationS, originally from Mozilla) takes a fundamentally different approach: encrypt files in place. Instead of encrypting whole files, SOPS encrypts only the values, leaving keys readable. This makes git diffs of SOPS-encrypted files surprisingly useful — you can see which key changed even when you can't decrypt the values.
# db-creds.enc.yaml (after SOPS encryption)
apiVersion: v1
kind: Secret
metadata:
name: db-creds
stringData:
username: ENC[AES256_GCM,data:7a3...,iv:abc...,tag:xyz...,type:str]
password: ENC[AES256_GCM,data:9d7...,iv:def...,tag:uvw...,type:str]
sops:
kms:
- arn: arn:aws:kms:us-east-1:123456:key/abc-def
created_at: "2026-04-01T10:00:00Z"
encrypted_regex: ^(data|stringData)$
Decryption integration
SOPS integrates with Helm via plugins, with Kustomize via the ksops plugin, with Terraform via the sops provider, and with raw kubectl via sops -d | kubectl apply. The encryption keys can be KMS (AWS, GCP, Azure), age (GPG-alternative), GPG, or plain PGP. Most teams use AWS KMS or age in 2026.
Strengths
- Works beyond Kubernetes: any deployment system that can run a decryption step works (Helm, Kustomize, Terraform, Ansible, plain shell scripts)
- Diffable encrypted files: keys are visible, only values are encrypted; you see the structural diff in PRs
- Multi-recipient encryption: encrypt once, decrypt with multiple keys (different team members, different KMS keys)
- Mature ecosystem: SOPS has been around since 2017; integrations exist for nearly every tool
Weaknesses
- Manual rotation: same as Sealed Secrets — rotate by re-encrypting and committing
- Key management is your problem: KMS access, age key rotation, GPG key management — none are trivial
- Per-environment complexity: typically you have prod-keys and staging-keys; managing access correctly is harder than it sounds
- Breaks "single command apply" flows: you need a wrapper that decrypts before apply; not all CI systems support that cleanly
Decision Matrix: Pick X If...
- Pick Sealed Secrets if: you're 100% on Kubernetes, you want secrets in Git as source of truth, you don't already run a secrets manager, and your rotation cadence is low (quarterly or less).
- Pick ESO if: you already have a cloud secrets manager (AWS, GCP, Azure, Vault, Doppler) as source of truth, you need automatic rotation propagation across multiple clusters, or you have audit/compliance requirements that demand external store logging.
- Pick SOPS if: your stack is Helm-heavy or extends beyond Kubernetes (Terraform, Ansible-managed servers, raw shell deploys), or you specifically need diffable encrypted files in PRs.
- Use both Sealed Secrets and ESO: rare but valid pattern — Sealed Secrets for cluster-bootstrap secrets (the secrets needed before ESO's auth works), ESO for everything else after the cluster is healthy.
Multi-Cluster Patterns
| Pattern | Sealed Secrets | ESO | SOPS |
|---|---|---|---|
| Same secret in 5 clusters | Re-seal 5 times (5 different cluster keys) | One ExternalSecret per cluster pointing same upstream | Re-encrypt for 5 KMS keys (or shared key) |
| Per-tenant secret in 50 clusters | 50 sealed manifests in Git | 50 ExternalSecrets, single upstream entry per tenant | 50 SOPS files |
| Auto rotation across all clusters | Manual: re-seal + commit triggers redeploy | Automatic: rotate upstream, ESO syncs in refreshInterval | Manual: re-encrypt + commit |
For multi-cluster fleets at scale (10+ clusters), ESO is dramatically less work than Sealed Secrets or SOPS. The trade-off: you're now operating an upstream store, which might be why you weren't using one in the first place. ArgoCD and FluxCD both integrate with all three patterns.
Common Pitfalls
- Sealed Secrets controller key loss: if you lose the controller's private key without backing it up, every existing SealedSecret in Git becomes undecryptable. Back up the master key (
kubectl get secret -n kube-system sealed-secrets-key) to a secure offline store. - ESO refreshInterval too tight: setting
refreshInterval: 30shammers your upstream store API. Default 1h is fine for 99% of use cases; only tune lower if you have latency-sensitive secret rotation needs. - SOPS without committing the unencrypted .sops.yaml: SOPS reads the
.sops.yamlconfig to know what keys to encrypt with. That file MUST be in Git. The encrypted output files MUST also be in Git. The unencrypted source file MUST NOT be. - Forgetting CRD upgrades: Sealed Secrets and ESO both ship CRDs. Helm chart upgrades sometimes don't update the CRDs (Helm's deliberate behavior). Manual CRD upgrade is required:
kubectl apply --force-conflicts -f path/to/crds.yaml. - Mixing patterns inconsistently: SealedSecret for some, ExternalSecret for others, SOPS for others. The cognitive cost of "where does this secret live?" eats engineering hours. Pick one pattern and stick with it for new work.
Pro tip: For most teams in 2026, ESO is the right default if you can stand up a cloud-native secret manager. The operational overhead of "yet another upstream store" is offset by automatic rotation propagation, multi-cluster sharing, and proper audit trails. Sealed Secrets is the right pick when you specifically don't want an external store. SOPS is the right pick when your stack genuinely extends beyond Kubernetes. The advanced multi-tenant secret patterns I deploy in production I send to the newsletter.
Frequently Asked Questions
What's the difference between Sealed Secrets and External Secrets Operator?
Sealed Secrets stores encrypted secrets in Git, decrypted by an in-cluster controller — pure GitOps with no external store. External Secrets Operator (ESO) syncs secrets from an upstream store (AWS Secrets Manager, Vault, etc.) into K8s, with the upstream as source of truth. Sealed Secrets needs no external service; ESO needs both the operator and the upstream store. Pick Sealed Secrets for pure GitOps; pick ESO when you already have a secret manager.
Should I use SOPS or Sealed Secrets?
SOPS works beyond Kubernetes (Helm, Terraform, Ansible) and produces diffable encrypted files that show structural changes in PRs. Sealed Secrets is Kubernetes-only but simpler to set up — single controller, single CRD. Pick SOPS for Helm-heavy or multi-tool workflows; pick Sealed Secrets for pure-Kubernetes deployments where you want minimal operational overhead.
Is External Secrets Operator GitOps-compatible?
Yes, but in a different way than Sealed Secrets. The ExternalSecret manifest (which points at upstream secrets) is in Git; the actual secret values live in the upstream store. Some teams call this "indirect GitOps" since the source-of-truth value is outside Git. ArgoCD and Flux both support ESO patterns natively.
What is kubeseal?
kubeseal is the CLI for Sealed Secrets. It takes a regular Kubernetes Secret manifest and encrypts it against the cluster's public key, producing a SealedSecret manifest you can commit to Git. The cluster controller decrypts it back into a Secret. kubeseal also has commands for re-sealing (after key rotation), validation, and key rotation operations.
Can I use SOPS with Helm?
Yes, via the helm-secrets plugin or by piping sops -d to helm install. The plugin integrates SOPS decryption into the Helm install/upgrade lifecycle automatically. Encrypted values files (typically named secrets.enc.yaml or secrets.yaml) live in Git; the plugin decrypts them before passing to Helm. This is one of the most common SOPS use cases in 2026.
Does External Secrets Operator support Vault?
Yes — ESO supports HashiCorp Vault as a SecretStore provider with multiple auth methods (Kubernetes service account, AppRole, JWT, token). Vault stays the source of truth; ESO syncs into K8s Secrets on a polling interval. This combination gives you Vault's dynamic secrets capability with K8s-native consumption pattern. ESO also supports AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, Doppler, Akeyless, and 15+ other backends.
What happens if Sealed Secrets controller crashes?
Existing decrypted Secrets in the cluster keep working — they're already native K8s Secrets. New SealedSecret manifests won't decrypt until the controller comes back. ArgoCD or Flux will retry on the next reconciliation. Mitigation: run multiple controller replicas (the controller supports HA mode) and monitor its health like any other critical workload.
Pick One Pattern, Stick with It
The biggest mistake teams make with GitOps secrets is mixing patterns inconsistently — Sealed Secrets for some, ESO for others, raw plaintext for "just-this-once." Pick the pattern that matches your constraints and apply it everywhere. Sealed Secrets when you want pure GitOps with no external store. ESO when you already operate a cloud secret manager. SOPS when your stack genuinely extends beyond Kubernetes. The right choice depends on whether you have an existing upstream store, your multi-cluster scale, and how often you rotate secrets.
Written by
Abhishek Patel
Infrastructure engineer with 10+ years building production systems on AWS, GCP, and bare metal. Writes practical guides on cloud architecture, containers, networking, and Linux for developers who want to understand how things actually work under the hood.
Related Articles
Multi-Cluster Kubernetes: Argo CD ApplicationSet Patterns
When 10+ clusters or 50+ services break hand-written GitOps. ApplicationSet's four generators (cluster list, Git directory, PR, cluster decision), real production patterns (env promotion, per-tenant, multi-region failover, preview envs), and the sharp edges (template debugging, cascading mistakes, RBAC).
11 min read
AI/ML EngineeringLLM Latency: TTFT, ITL, and Why End-User Latency Isn't What You Think
LLM latency decomposes into TTFT (time to first token, 300-1500ms), ITL (inter-token, 10-30ms), and total time. Each has different causes and fixes. Why streaming dominates UX, when Cerebras/Groq beat Claude on speed, and the optimization playbook.
11 min read
DevOpsPython uv vs pip vs Poetry vs PDM: Speed Benchmarks 2026
Real benchmarks: uv installs Django + ML stack in 8s vs pip's 90s, Poetry's 50s, PDM's 38s. Why uv is fast (Rust + parallelism + PubGrub), what pip still does that uv doesn't, migration paths, and where Poetry's ergonomics still win.
12 min read
Enjoyed this article?
Get more like this in your inbox. No spam, unsubscribe anytime.