Skip to main content
Sealed Secrets allows you to encrypt Kubernetes secrets so they can be safely stored in Git repositories. The sealed secrets are decrypted only within the cluster.

Overview

Sealed Secrets consists of:
  • Controller: Runs in your cluster and decrypts sealed secrets
  • kubeseal: CLI tool for encrypting secrets locally
Learn more in the Sealed Secrets repository.

Installation

Install Sealed Secrets Controller

Install the controller using 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
Find the latest version on ArtifactHub.

Install kubeseal CLI

Install the kubeseal command-line tool:
KUBESEAL_VERSION='0.29.0'  # Check for latest version
curl -OL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
tar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
sudo install -m 755 kubeseal /usr/local/bin/kubeseal

Verify Installation

Fetch the public certificate to confirm the controller is running:
kubeseal --fetch-cert
You should see the public certificate output.

Creating Sealed Secrets

Method 1: From Literal Values

Create a sealed secret directly from literal values:
kubectl create secret generic secret-name \
  --dry-run=client \
  --from-literal=foo=bar \
  -o yaml | \
kubeseal \
  --controller-name=sealed-secrets-controller \
  --controller-namespace=kube-system \
  --format yaml > mysealedsecret.yaml
This creates a sealed secret file that can be committed to Git.

Method 2: From Existing Secret File

If you have an existing secret YAML file:
kubeseal --format yaml < sample-secret.yml > sealed_secret.yml

Method 3: Offline Encryption (No Cluster Access)

For air-gapped environments or CI/CD pipelines, fetch the certificate once:
kubeseal \
  --controller-name=sealed-secrets-controller \
  --controller-namespace=kube-system \
  --fetch-cert > mycert.pem
Then encrypt secrets using the local certificate:
kubectl create secret generic secret-name \
  --dry-run=client \
  --from-literal=foo=bar \
  -o yaml | \
kubeseal \
  --controller-name=sealed-secrets-controller \
  --controller-namespace=kube-system \
  --format yaml \
  --cert mycert.pem > mysealedsecret.yaml
The sealed secret file (mysealedsecret.yaml) is safe to commit to version control.

Applying Sealed Secrets

Apply the sealed secret to your cluster:
kubectl apply -f sealed_secret.yml
The controller automatically decrypts it and creates a regular Kubernetes secret.

Verify the Secret

Check that the secret was created:
kubectl get secret
kubectl get secret exchange-router-secret -o yaml
View the decrypted secret:
kubectl get secret secret-name -o jsonpath='{.data.foo}' | base64 --decode

Best Practices for GitOps

Store Only Sealed Secrets in Git

Never commit unencrypted Kubernetes secrets to Git. Always use sealed secrets.
# Good - sealed secret (safe to commit)
kubectl create secret generic db-credentials \
  --dry-run=client \
  --from-literal=password=my-secure-password \
  -o yaml | \
kubeseal --format yaml > db-sealed-secret.yaml

git add db-sealed-secret.yaml
git commit -m "Add database credentials as sealed secret"

Use Namespace Scoping

By default, sealed secrets are namespace-scoped for security:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: mysecret
  namespace: production  # Sealed secret tied to this namespace
spec:
  encryptedData:
    password: AgBh...

Rotate Secrets Regularly

  1. Create a new sealed secret with updated values
  2. Apply the new sealed secret
  3. Update the old sealed secret file in Git
  4. Restart pods that use the secret if needed

Backup the Sealing Key

The controller’s private key is critical for decryption:
kubectl get secret -n kube-system sealed-secrets-key -o yaml > sealed-secrets-key-backup.yaml
Store this backup securely outside your cluster. Without it, you cannot decrypt your sealed secrets if you lose the cluster.

Naming Convention

Both the SealedSecret and the generated Secret must have the same name and namespace.

Example Workflow

Here’s a complete workflow for adding a secret to your GitOps repository:
# 1. Create sealed secret
kubectl create secret generic app-secrets \
  --dry-run=client \
  --from-literal=api-key=my-api-key \
  --from-literal=db-password=my-db-pass \
  -n production \
  -o yaml | \
kubeseal \
  --controller-name=sealed-secrets-controller \
  --controller-namespace=kube-system \
  --format yaml > sealed-app-secrets.yaml

# 2. Commit to Git
git add sealed-app-secrets.yaml
git commit -m "Add application secrets"
git push

# 3. ArgoCD automatically applies it, controller decrypts
# 4. Verify secret exists
kubectl get secret app-secrets -n production

Troubleshooting

Controller Not Decrypting

Check controller logs:
kubectl logs -n kube-system -l app.kubernetes.io/name=sealed-secrets

Certificate Not Found

Ensure the controller is running:
kubectl get pods -n kube-system | grep sealed-secrets

Secret Not Created After Applying

Check the SealedSecret resource:
kubectl get sealedsecrets
kubectl describe sealedsecret <name>

Additional Resources

Sealed Secrets Tutorial

Video walkthrough of Sealed Secrets with KodeKloud

Build docs developers (and LLMs) love