GitOps Secrets Management: Sealed Secrets vs. External Secrets Operator

Introduction

GitOps promises a single source of truth: everything in Git, everything versioned, everything auditable. But there’s an obvious problem—you can’t commit secrets to Git. Database passwords, API keys, TLS certificates—these need to exist in your cluster, but they can’t live in your repository in plaintext.

This tension has spawned an entire category of tools designed to bridge the gap between GitOps principles and secret management reality. Two approaches have emerged as the dominant solutions in the Kubernetes ecosystem: Sealed Secrets and the External Secrets Operator (ESO).

This article compares both approaches, explains when to use each, and provides practical implementation guidance for teams adopting GitOps in 2026.

The GitOps Secrets Problem

In a traditional deployment model, secrets are injected at deploy time—CI/CD pipelines pull from Vault, inject into Kubernetes, done. But GitOps inverts this model: the cluster pulls its desired state from Git. If secrets aren’t in Git, how does the cluster know what secrets to create?

Three fundamental approaches have emerged:

  1. Encrypt secrets in Git: Store encrypted secrets in the repository; decrypt them in-cluster (Sealed Secrets, SOPS)
  2. Reference external stores: Store pointers to secrets in Git; fetch actual values from external systems at runtime (External Secrets Operator)
  3. Hybrid approaches: Combine encryption with external references for different use cases

Sealed Secrets: Encryption at Rest in Git

Sealed Secrets, created by Bitnami, uses asymmetric encryption to allow secrets to be safely committed to Git.

How It Works

┌─────────────────────────────────────────────────────────────┐
│                    SEALED SECRETS FLOW                      │
│                                                             │
│   Developer          Git Repo           Kubernetes          │
│       │                  │                   │              │
│       │  kubeseal       │                   │              │
│       │ ──────────►     │                   │              │
│       │  (encrypt)      │   SealedSecret    │              │
│       │                 │ ───────────────►  │              │
│       │                 │    (GitOps sync)  │              │
│       │                 │                   │  Controller  │
│       │                 │                   │  decrypts    │
│       │                 │                   │  ──────────► │
│       │                 │                   │    Secret    │
└─────────────────────────────────────────────────────────────┘
  1. A controller runs in your cluster, generating a public/private key pair
  2. Developers use kubeseal CLI to encrypt secrets with the cluster’s public key
  3. The encrypted SealedSecret resource is committed to Git
  4. Argo CD or Flux syncs the SealedSecret to the cluster
  5. The Sealed Secrets controller decrypts it, creating a standard Kubernetes Secret

Installation

# Install the controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system

# Install kubeseal CLI
brew install kubeseal  # macOS
# or download from GitHub releases

Creating a Sealed Secret

# Create a regular secret (don't commit this!)
kubectl create secret generic db-creds   --from-literal=username=admin   --from-literal=password=supersecret   --dry-run=client -o yaml > secret.yaml

# Seal it (this is safe to commit)
kubeseal --format yaml < secret.yaml > sealed-secret.yaml

# The output looks like:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-creds
  namespace: default
spec:
  encryptedData:
    username: AgBy8hCi8... # encrypted
    password: AgCtr9dk3... # encrypted

Pros and Cons

Advantages:

  • Simple mental model: „encrypt, commit, done“
  • No external dependencies at runtime
  • Works offline—no network calls to external systems
  • Secrets are genuinely in Git (encrypted), enabling full GitOps audit trail
  • Lightweight controller with minimal resource usage

Disadvantages:

  • Cluster-specific encryption: secrets must be re-sealed for each cluster
  • Key rotation is manual and requires re-sealing all secrets
  • No automatic secret rotation from external sources
  • Single point of failure: lose the private key, lose all secrets
  • Doesn’t integrate with existing enterprise secret stores (Vault, AWS Secrets Manager)

External Secrets Operator: References to External Stores

The External Secrets Operator (ESO) takes a different approach: instead of encrypting secrets, it stores references to secrets in Git. The actual secret values live in external secret management systems.

How It Works

┌─────────────────────────────────────────────────────────────┐
│              EXTERNAL SECRETS OPERATOR FLOW                 │
│                                                             │
│   Git Repo              Kubernetes         Secret Store     │
│       │                     │                   │           │
│   ExternalSecret           │                   │           │
│   (reference)              │                   │           │
│       │ ────────────────►  │                   │           │
│       │    (GitOps sync)   │   ESO Controller  │           │
│       │                    │ ────────────────► │           │
│       │                    │   (fetch secret)  │           │
│       │                    │ ◄──────────────── │           │
│       │                    │   (secret value)  │           │
│       │                    │                   │           │
│       │                    │   Creates K8s     │           │
│       │                    │   Secret          │           │
└─────────────────────────────────────────────────────────────┘
  1. You define an ExternalSecret resource that references a secret in an external store
  2. The ExternalSecret is committed to Git and synced to the cluster
  3. ESO’s controller fetches the actual secret value from the external store
  4. ESO creates a standard Kubernetes Secret with the fetched values
  5. ESO periodically refreshes the secret, enabling automatic rotation

Supported Providers (20+)

ESO supports a vast ecosystem of secret stores:

  • HashiCorp Vault (KV, PKI, database secrets engines)
  • AWS Secrets Manager and Parameter Store
  • Azure Key Vault
  • Google Cloud Secret Manager
  • 1Password, Doppler, Infisical
  • CyberArk, Akeyless
  • And many more…

Installation

# Install External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace

Configuration Example: AWS Secrets Manager

# 1. Create a SecretStore (cluster-wide) or ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets-manager
spec:
  provider:
    aws:
      service: SecretsManager
      region: eu-central-1
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets-sa
            namespace: external-secrets

---
# 2. Create an ExternalSecret that references AWS
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  refreshInterval: 1h  # Auto-refresh every hour
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-credentials  # Name of the K8s Secret to create
  data:
    - secretKey: username
      remoteRef:
        key: production/database
        property: username
    - secretKey: password
      remoteRef:
        key: production/database
        property: password

Pros and Cons

Advantages:

  • Integrates with enterprise secret management (Vault, cloud providers)
  • Automatic secret rotation—just update the source, ESO syncs
  • Centralized secret management across multiple clusters
  • No secrets in Git at all—not even encrypted
  • Supports 20+ providers out of the box
  • CNCF project with active community

Disadvantages:

  • Runtime dependency on external secret store
  • More complex setup (authentication to external providers)
  • If the secret store is down, new secrets can’t be created
  • Audit trail split between Git (references) and secret store (values)
  • Higher resource usage than Sealed Secrets

SOPS: A Third Approach

SOPS (Secrets OPerationS) by Mozilla deserves mention as a popular alternative. Like Sealed Secrets, it encrypts secrets for storage in Git—but with key differences:

  • Encrypts only the values in YAML/JSON, leaving keys readable
  • Supports multiple key management systems (AWS KMS, GCP KMS, Azure Key Vault, PGP, age)
  • Not Kubernetes-specific—works with any configuration files
  • Integrates with Argo CD and Flux via plugins
# SOPS-encrypted secret (keys visible, values encrypted)
apiVersion: v1
kind: Secret
metadata:
  name: db-creds
stringData:
  username: ENC[AES256_GCM,data:admin,iv:...,tag:...]
  password: ENC[AES256_GCM,data:supersecret,iv:...,tag:...]
sops:
  kms:
    - arn: arn:aws:kms:eu-central-1:123456789:key/abc-123

Decision Framework: Which Should You Use?

Factor Sealed Secrets External Secrets Operator SOPS
Existing Vault/Cloud KMS ❌ Not integrated ✅ Native support ⚠️ For encryption only
Multi-cluster ❌ Re-seal per cluster ✅ Centralized store ⚠️ Shared keys needed
Secret rotation ❌ Manual ✅ Automatic ❌ Manual
Offline/air-gapped ✅ Works offline ❌ Needs connectivity ✅ Works offline
Complexity Low Medium-High Medium
Secrets in Git Encrypted References only Encrypted
Enterprise compliance ⚠️ Limited audit ✅ Full audit trail ⚠️ Depends on KMS

Use Sealed Secrets When:

  • You’re a small team without enterprise secret management
  • You have a single cluster or few clusters
  • You need simplicity over features
  • Air-gapped or offline environments

Use External Secrets Operator When:

  • You already use Vault, AWS Secrets Manager, or similar
  • You need automatic secret rotation
  • You manage multiple clusters
  • Compliance requires centralized secret management
  • You want zero secrets in Git (even encrypted)

Use SOPS When:

  • You need to encrypt non-Kubernetes configs too
  • You want cloud KMS without full ESO complexity
  • You prefer visible structure with encrypted values

GitOps Integration: Argo CD and Flux

Argo CD with Sealed Secrets

Sealed Secrets work natively with Argo CD—just commit SealedSecrets to your repo:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
spec:
  source:
    repoURL: https://github.com/myorg/my-app
    path: k8s/
    # SealedSecrets in k8s/ are synced and decrypted automatically

Argo CD with External Secrets Operator

ESO also works seamlessly—ExternalSecrets are synced, and ESO creates the actual Secrets:

# In your Git repo
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault
    kind: ClusterSecretStore
  target:
    name: app-secrets
  dataFrom:
    - extract:
        key: secret/data/my-app

Flux with SOPS

Flux has native SOPS support via the Kustomization resource:

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
spec:
  decryption:
    provider: sops
    secretRef:
      name: sops-age  # Key stored as K8s secret

Best Practices for 2026

  1. Never commit plaintext secrets. This seems obvious, but git history is forever. Use pre-commit hooks to catch accidents.
  2. Rotate secrets regularly. ESO makes this easy; Sealed Secrets requires re-sealing. Automate either way.
  3. Use namespaced secrets. Don’t create cluster-wide secrets unless absolutely necessary. Principle of least privilege applies.
  4. Monitor secret access. Enable audit logging in your secret store. Know who accessed what, when.
  5. Plan for key rotation. Sealed Secrets keys, SOPS keys, ESO service account credentials—all need rotation procedures.
  6. Test secret recovery. Can you recover if you lose access to your secret store? Document and test disaster recovery.
  7. Consider secret sprawl. As you scale, centralized management (ESO + Vault) becomes more valuable than per-cluster approaches.

Conclusion

GitOps and secrets management are fundamentally at tension—Git wants everything versioned and public within the org; secrets want to be hidden and ephemeral. Both Sealed Secrets and External Secrets Operator resolve this tension, but in different ways.

Sealed Secrets embraces encryption: secrets live in Git, but only the cluster can read them. External Secrets Operator embraces indirection: Git contains references, and runtime systems fetch the actual values.

For most organizations in 2026, External Secrets Operator is the strategic choice. It integrates with enterprise secret management, enables automatic rotation, and scales across clusters. But Sealed Secrets remains valuable for simpler deployments, air-gapped environments, and teams just starting their GitOps journey.

The worst choice? No choice at all—plaintext secrets in Git, or manual secret creation that bypasses GitOps entirely. Pick an approach, implement it consistently, and your GitOps practice will be both secure and auditable.