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:
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
Create a new sealed secret with updated values
Apply the new sealed secret
Update the old sealed secret file in Git
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 < nam e >
Additional Resources
Sealed Secrets Tutorial Video walkthrough of Sealed Secrets with KodeKloud