Sealed Secrets Reference: kubeseal, GitOps-Safe K8s Secrets, Key Rotation & Scopes
Sealed Secrets is a Bitnami/CNCF project for storing encrypted Kubernetes Secrets safely in Git. The kubeseal CLI encrypts a Secret with the cluster’s public key — only the SealedSecrets controller in that cluster can decrypt it. The encrypted SealedSecret can live in your Git repo without any secrets leaking.
1. Sealed Secrets vs External Secrets Operator
GitOps-first vs external store — which fits your workflow
| Approach | Where secrets live | Best for |
|---|---|---|
| Sealed Secrets | Encrypted in Git (SealedSecret YAML) | Pure GitOps teams — everything including secrets lives in Git |
| External Secrets Operator | Vault/AWS SM/GCP/Azure (synced to K8s) | Teams with existing centralised secret store |
| Vault Agent | HashiCorp Vault (injected as files) | Short-lived dynamic credentials per pod |
# Install SealedSecrets controller: helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets helm install sealed-secrets sealed-secrets/sealed-secrets --namespace kube-system # Install kubeseal CLI: brew install kubeseal # macOS # Or: curl -OL https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/kubeseal-linux-amd64 # Verify controller is running: kubectl get pods -n kube-system -l app.kubernetes.io/name=sealed-secrets
2. Creating SealedSecrets
Encrypt a secret with kubeseal — safe to commit to Git
# 1. Create a regular K8s Secret (YAML — DON'T apply this to cluster):
kubectl create secret generic my-app-secrets --from-literal=db-password=supersecret123 --from-literal=api-key=sk_live_abcdef --namespace production --dry-run=client -o yaml > my-secret.yaml
# 2. Encrypt with kubeseal (fetches public key from cluster):
kubeseal --format yaml < my-secret.yaml > my-sealed-secret.yaml
# 3. Inspect the output (safe to read — encrypted values):
cat my-sealed-secret.yaml
# apiVersion: bitnami.com/v1alpha1
# kind: SealedSecret
# metadata:
# name: my-app-secrets
# namespace: production
# spec:
# encryptedData:
# db-password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...
# api-key: AgCHkQSM4...
# 4. Apply to cluster (controller decrypts → creates real K8s Secret):
kubectl apply -f my-sealed-secret.yaml
# 5. Commit my-sealed-secret.yaml to Git — it's safe
git add my-sealed-secret.yaml
git commit -m "Add encrypted DB credentials for production"
# Your pods use the resulting K8s Secret normally:
envFrom:
- secretRef:
name: my-app-secrets # same name as the SealedSecret
3. Scopes — Namespace & Cluster-Wide
Control where a SealedSecret can be decrypted
# Default scope: strict — SealedSecret is bound to exact name + namespace # Cannot be renamed or moved to different namespace kubeseal --format yaml < secret.yaml > sealed.yaml # strict (default) # Namespace scope: can be renamed within the same namespace kubeseal --scope namespace-wide --format yaml < secret.yaml > sealed.yaml # Cluster scope: can be applied to any namespace with any name kubeseal --scope cluster-wide --format yaml < secret.yaml > sealed.yaml # Check scope of an existing SealedSecret: kubectl get sealedsecret my-app-secrets -n production -o yaml | grep sealedsecrets.bitnami.com/cluster-wide # Fetch the public key offline (useful for CI — no live cluster access needed): kubeseal --fetch-cert > my-cluster-cert.pem # Seal offline using the cert: kubeseal --cert my-cluster-cert.pem --format yaml < secret.yaml > sealed.yaml
4. Key Rotation & Re-encryption
Rotate encryption keys and re-seal existing secrets
# SealedSecrets automatically generates new keys every 30 days
# Old keys are retained — existing SealedSecrets still decrypt
# List encryption keys:
kubectl get secrets -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key
# Force a new key now (for rotation after suspected compromise):
kubectl create secret generic --dry-run=client -n kube-system -o json sealed-secrets-keySECRET | kubectl annotate -f - sealedsecrets.bitnami.com/sealed-secrets-key=active --local -o yaml | kubectl apply -f -
kubectl rollout restart deployment sealed-secrets -n kube-system
# Re-encrypt all SealedSecrets with the latest key:
# (important after rotation — old keys may be retired)
# 1. Fetch new cert:
kubeseal --fetch-cert > new-cert.pem
# 2. For each SealedSecret in Git:
# - kubectl get secret $NAME -n $NS -o yaml | kubeseal --cert new-cert.pem > new-sealed.yaml
# - Replace old file in Git, commit, push
# View decrypted secret (to verify before rotation):
kubectl get secret my-app-secrets -n production -o jsonpath='{.data.db-password}' | base64 -d
5. GitOps Integration
ArgoCD and Flux patterns with SealedSecrets
# ArgoCD integration: # 1. ArgoCD syncs SealedSecrets from Git (just another CRD) # 2. SealedSecrets controller converts them to K8s Secrets # 3. Apps reference the K8s Secrets normally # No special ArgoCD plugin needed — it works out of the box # Flux integration: # Same pattern — Flux watches Git, applies SealedSecret CRDs # SealedSecrets controller handles decryption # Directory structure: # k8s/ # base/ # deployment.yaml # service.yaml # my-sealed-secret.yaml # encrypted, safe in Git # overlays/ # production/ # kustomization.yaml # Workflow for secret update: # 1. Update the secret value kubectl create secret generic my-app-secrets --from-literal=db-password=newpassword456 --namespace production --dry-run=client -o yaml | kubeseal --format yaml > my-sealed-secret.yaml # 2. Commit the updated SealedSecret # 3. ArgoCD/Flux syncs it → controller updates the real Secret # 4. Pods pick up the new value on restart or via Secret rotation mechanism # Bootstrap a new cluster: # Export old controller's key: kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > key-backup.yaml # Import to new cluster: kubectl apply -f key-backup.yaml # Now the new cluster can decrypt your existing Git-stored SealedSecrets
Track Sealed Secrets and Kubernetes security tool releases.
ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.
Related: External Secrets Operator Reference | ArgoCD & GitOps Reference | Flux v2 Reference
🔍 Free tool: K8s YAML Security Linter — check the K8s manifests that consume your SealedSecrets for security misconfigurations.
Founded
2023 in London, UK
Contact
hello@releaserun.com