Secrets Management Comparison — Vault vs ESO vs SOPS¶
Canonical comparison of the three dominant Kubernetes secrets management approaches.
Quick Reference¶
| Dimension | HashiCorp Vault | External Secrets Operator | SOPS |
|---|---|---|---|
| Type | Centralized secrets platform | K8s sync operator | File encryption tool |
| Latest Version | v1.21.4 (Mar 2026) | v2.2.0 (Mar 2026) | CNCF Sandbox |
| Architecture | Client-server (standalone) | K8s controller (bridges providers) | CLI (no daemon) |
| Dynamic secrets | ✅ Yes (auto-generated, short-lived) | ❌ (fetches static secrets) | ❌ |
| Encryption-as-a-service | ✅ Transit engine | ❌ | ❌ |
| PKI / Cert management | ✅ | ❌ | ❌ |
| License | ⚠️ BSL 1.1 | Apache 2.0 | MPL 2.0 |
| Operational cost | High (deploy + manage cluster) | Low (K8s operator) | Minimal (CLI only) |
How They Work Together¶
flowchart LR
SOPS_C["SOPS\n(encrypt secrets\nin Git)"] -->|"encrypted YAML\nin Git repo"| GitOps["GitOps\n(Flux / ArgoCD)"]
GitOps -->|"deploy\nExternalSecret CRDs"| ESO_C["ESO\n(sync to K8s Secrets)"]
ESO_C -->|"fetch dynamic\ncredentials"| Vault_C["Vault\n(generate secrets)"]
Vault_C -->|"short-lived\nDB creds"| App["Application"]
style SOPS_C fill:#2e7d32,color:#fff
style ESO_C fill:#1565c0,color:#fff
style Vault_C fill:#000,color:#fff
Key insight: These tools are complementary, not competitive. The gold-standard pattern is: SOPS encrypts configs in Git → ESO syncs external secrets to K8s → Vault generates dynamic credentials.
Feature Matrix¶
| Feature | Vault | ESO | SOPS |
|---|---|---|---|
| Secret storage | ✅ (KV, databases, cloud) | ❌ (bridges external stores) | ✅ (encrypted in Git) |
| Dynamic secrets | ✅ (DB, AWS, GCP, Azure) | ❌ | ❌ |
| Secret rotation | ✅ Automatic | ✅ (poll-based sync) | ❌ Manual |
| Audit logging | ✅ Comprehensive | ❌ | ❌ |
| K8s native | ⚠️ (needs Vault agent/CSI) | ✅ Native K8s Secrets | ❌ (pre-deployment) |
| GitOps compatible | ⚠️ (not stored in Git) | ✅ CRDs in Git | ✅ Encrypted files in Git |
| Multi-provider | N/A (is the provider) | ✅ (Vault, AWS, GCP, Azure) | ✅ (age, AWS KMS, GCP KMS) |
| Complexity | High | Low–Medium | Low |
Decision Guide¶
| Scenario | Recommendation |
|---|---|
| Simple GitOps secrets | SOPS — encrypt in Git, decrypt on deploy |
| Multi-provider K8s secret sync | ESO — bridges any external provider to K8s |
| Dynamic DB credentials | Vault — auto-generated, short-lived, auto-revoked |
| PKI / certificate management | Vault — full CA, intermediate CAs |
| Small team, low complexity | SOPS + ESO |
| Enterprise, regulated | Vault + ESO |
| Full gold-standard stack | SOPS (Git) + ESO (sync) + Vault (generate) |
Integration Patterns¶
SOPS + Flux GitOps Pipeline¶
The most common GitOps secrets pattern. SOPS encrypts secret values in YAML (keys remain plaintext for readable diffs), and Flux decrypts them on-cluster before applying.
sequenceDiagram
participant Dev as Developer
participant Git as Git Repository
participant Flux as Flux Source Controller
participant Kust as Kustomize Controller
participant SOPS as SOPS Decryption
participant K8s as Kubernetes API
Dev->>Dev: sops --encrypt secret.yaml
Dev->>Git: git push (encrypted YAML)
Flux->>Git: Pull encrypted manifests
Flux->>Kust: Forward to Kustomize Controller
Kust->>SOPS: Detect SOPS metadata, decrypt
SOPS->>K8s: Apply plaintext Secret
Key configuration:
- Create
.sops.yamlin repo root definingcreation_ruleswithencrypted_regexand public key - Store the age/GPG private key as a K8s Secret in the
flux-systemnamespace - Configure
KustomizationCRD withspec.decryption.provider: sops - Use
ageover PGP for simplicity; graduate to cloud KMS (AWS KMS, GCP KMS) for production
Never apply encrypted Secrets with kubectl
SOPS-encrypted Secrets are designed for consumption by kustomize-controller. Applying them directly with kubectl apply will create a Secret containing ciphertext, not plaintext.
ESO + Vault with Kubernetes Auth¶
ESO bridges Vault's dynamic secrets engine to native Kubernetes Secrets. The Kubernetes auth method is the recommended production pattern.
sequenceDiagram
participant ESO as ESO Controller
participant K8s as K8s API (ServiceAccount)
participant Vault as Vault Server
participant App as Application Pod
ESO->>K8s: Read ServiceAccount JWT
ESO->>Vault: POST /auth/kubernetes/login (JWT)
Vault->>Vault: Validate JWT against K8s API
Vault-->>ESO: Return Vault token
ESO->>Vault: GET /secret/data/myapp (KV v2)
Vault-->>ESO: Return secret payload
ESO->>K8s: Create/Update Kubernetes Secret
App->>K8s: Mount Secret as env/volume
Key configuration:
- Enable Vault Kubernetes auth:
vault auth enable kubernetes - Bind ESO ServiceAccount to a Vault role with scoped policy
- Create
SecretStoreCRD referencing Vault server URL + K8s auth mount - Create
ExternalSecretCRD mapping Vault paths to K8s Secret keys - Set
refreshInterval(e.g.,1h) for automatic rotation sync
Full Stack: SOPS + ESO + Vault¶
The "gold standard" pattern combines all three tools, each handling what it does best:
| Layer | Tool | Responsibility |
|---|---|---|
| Git encryption | SOPS | Encrypt non-sensitive configs and ESO CRDs in Git |
| K8s sync | ESO | Sync external secrets to native K8s Secrets |
| Secret generation | Vault | Generate dynamic, short-lived credentials (DB, cloud) |
Workflow: Developer commits SOPS-encrypted ExternalSecret CRDs to Git. Flux decrypts and applies them. ESO reads the ExternalSecret, fetches the actual credential from Vault, and creates a K8s Secret. The application consumes the K8s Secret normally.
Start simple, add layers as needed
Most teams should start with SOPS only (simplest). Add ESO when you need multi-provider sync or rotation. Add Vault when you need dynamic credentials or PKI. Do not adopt the full stack on day one.
Operational Overhead¶
| Dimension | Vault | ESO | SOPS |
|---|---|---|---|
| Deployment | Helm chart + HA backend (Raft or Consul) | Single Helm chart (one controller + CRDs) | CLI binary (no deployment) |
| Infrastructure | 3-5 node cluster (HA), dedicated storage | Runs as K8s Deployment (1-3 replicas) | None |
| Init/unseal | Manual unseal or auto-unseal via cloud KMS | N/A | N/A |
| Maintenance | Regular: rotate root token, audit policies, manage leases, certificate renewal | Low: monitor CRD sync status, upgrade operator | Minimal: rotate encryption keys periodically |
| Backup | Raft snapshots + storage backend backups | CRDs in Git (self-recovering) | Keys in secure storage; encrypted files in Git |
| Team skill requirement | High — needs dedicated platform team | Low-Medium — K8s operator familiarity | Low — CLI/GitOps familiarity |
| On-call burden | High — unsealing, lease management, audit log review | Low — controller restart resolves most issues | Near-zero — no running processes |
| Estimated FTE | 0.25-0.5 FTE for operations | 0.05 FTE | ~0 FTE |
Cost Analysis¶
Licensing¶
| Tool | License | Open Source? | Notes |
|---|---|---|---|
| Vault Community | BSL 1.1 | Source-available (not OSI-approved) | Free for internal use; cannot host as competing service |
| Vault Enterprise | Proprietary | No | Adds namespaces, DR replication, HSM, Sentinel |
| ESO | Apache 2.0 | Yes | Fully open source, CNCF-affiliated |
| SOPS | MPL 2.0 | Yes | CNCF Sandbox project |
Infrastructure Costs¶
| Scenario | Vault (self-hosted) | Vault (HCP Dedicated) | ESO | SOPS |
|---|---|---|---|---|
| Small (< 10 services) | 3 EC2 t3.medium (~$100/mo) | ~$450/mo (dev cluster) | Free (runs in existing K8s) | Free |
| Medium (50 services) | 3 EC2 m5.large (~$300/mo) | ~$5,000/mo (Essentials + per-client fees) | Free | Free |
| Large (200+ services) | 5 EC2 m5.xlarge (~$700/mo) | Custom pricing (negotiate) | Free | Free |
HCP Vault per-client fees add up fast
HCP Vault Dedicated charges $72.92/month per client (any unique authenticating service/user) on Essentials and Standard tiers. At 50 clients, per-client fees alone cost $3,646/month — on top of cluster costs. Self-hosted Vault avoids this but requires operational expertise.
Total Cost by Team Size¶
| Team Size | Recommended Stack | Estimated Annual Cost |
|---|---|---|
| 1-5 engineers | SOPS only | ~$0 (just key management) |
| 5-20 engineers | SOPS + ESO | ~$0 infrastructure + team time |
| 20-50 engineers | ESO + Vault (self-hosted) | ~$5,000-15,000/yr infrastructure |
| 50+ engineers, regulated | Full stack (SOPS + ESO + Vault Enterprise) | $50,000-150,000/yr (Vault Enterprise license) |
Multi-cluster Secret Distribution¶
| Capability | Vault | ESO | SOPS |
|---|---|---|---|
| Architecture | Single Vault cluster serves all K8s clusters | ESO instance per cluster, all pointing to same provider | Encrypted files in shared Git repo |
| Cross-cluster sync | Native — each cluster authenticates independently | Per-cluster ESO + shared SecretStore backend | Git-based — each cluster decrypts independently |
| PushSecret | N/A (pull model) | ESO PushSecret CRD pushes to remote clusters |
N/A |
| Consistency | Strong — single source of truth in Vault | Eventual (poll interval, default 1h) | Manual — requires Git push + Flux reconcile |
| Secret scoping | Vault namespaces + policies per cluster | Namespace-scoped SecretStore per tenant |
.sops.yaml creation rules per path |
| Failure mode | Cluster loses access if Vault is down | Secrets persist in K8s; no updates until ESO reconnects | Secrets persist; no updates until Git sync |
Multi-cluster Patterns¶
Centralized Vault (hub-and-spoke):
- Single Vault cluster with Kubernetes auth enabled per target cluster
- Each cluster's ESO authenticates via its own ServiceAccount
- Vault policies scope access per cluster/namespace
- Best for: regulated environments needing centralized audit
Distributed ESO with shared backend:
- ESO deployed in every cluster, each with
ClusterSecretStorepointing to same provider (AWS SM, GCP SM, Azure KV) - Secrets are defined once in the provider, synced everywhere
- Best for: cloud-native teams using managed secret stores
Git-centric (SOPS + Flux):
- Encrypted secrets in a shared Git repo with per-cluster overlays
- Each cluster runs Flux and decrypts with its own age key
- Best for: teams that want everything in Git, no external dependencies
Compliance Considerations¶
| Framework | Vault | ESO | SOPS |
|---|---|---|---|
| SOC 2 | Strong — comprehensive audit logging, access controls, encryption at rest/transit, lease management | Partial — inherits audit from backend provider; add K8s audit logging | Partial — Git commit log as audit trail; no runtime audit |
| HIPAA | Strong — encryption-as-a-service (Transit engine), dynamic credentials, automatic rotation, detailed audit logs | Depends on backend — if backed by Vault or AWS SM, inherits their HIPAA posture | Weak — no automatic rotation, no access audit, manual key management |
| PCI-DSS | Strong — key management (HSM support in Enterprise), access controls, tokenization via Transit engine | Partial — no native key management; depends on backend | Weak — no key ceremony support, no runtime access controls |
| FedRAMP | HCP Vault Dedicated is FedRAMP authorized (via AWS GovCloud) | N/A — not an independent service | N/A |
| GDPR | Supports data residency via Vault namespaces | Depends on backend provider's data residency | Encryption keys can be region-scoped via cloud KMS |
Compliance does not mean just one tool
No single tool satisfies all compliance requirements alone. Vault provides the strongest audit and key management story, but your compliance posture also depends on K8s RBAC, network policies, cloud provider controls, and organizational processes. Use ESO and SOPS to complement Vault, not replace it.
Sources¶
- Vault Documentation
- External Secrets Operator Documentation
- SOPS Documentation
- Vault GitHub
- External Secrets Operator GitHub
- SOPS GitHub
- Flux SOPS Integration Guide
- Flux Secrets Management Overview
- HCP Vault Dedicated Pricing
- Vault Pricing Analysis (Infisical)
- ESO Kubernetes Provider (Cross-Cluster)
- Tailscale: Sync K8s Secrets Across Clusters with ESO