Introduction to Sealed Secrets #
One of the biggest challenges in GitOps is managing secrets securely. While we can store all our Kubernetes manifests in Git repositories, we cannot store plain-text secrets there due to security concerns. This creates a gap in our “everything in Git” philosophy of GitOps.
Sealed Secrets, developed by Bitnami, solves this problem by providing a way to encrypt secrets that can be safely stored in Git repositories and automatically decrypted by your Kubernetes cluster.
The Secret Management Problem in GitOps #
In traditional GitOps workflows, you might have configuration like this:
apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
data:
  username: YWRtaW4=  # base64 encoded 'admin'
  password: UGFzc3dvcmQxMjM=  # base64 encoded 'Password123'
The problem is that base64 encoding is not encryption - anyone with access to your Git repository can easily decode these values. This means you either:
- Store secrets outside Git - breaking GitOps principles
 - Use a separate secret management tool - adding complexity
 - Accept the security risk - not recommended
 
How Sealed Secrets Works #
Sealed Secrets introduces two key components:
- Controller: Runs in your cluster and holds a private key
 - kubeseal CLI: Encrypts secrets using the controller’s public key
 
Here’s the workflow:
- You create a regular Secret manifest
 - Use 
kubesealto encrypt it into a SealedSecret - Store the SealedSecret in Git (it’s safe - only your cluster can decrypt it)
 - The controller watches for SealedSecrets and automatically creates the corresponding Secret resources
 
The encrypted SealedSecret looks like this:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: database-credentials
spec:
  encryptedData:
    username: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEQAx...
    password: AgAKAoiQm0XBmKUCOTfdPGdKnVL4n4OTU...
  template:
    metadata:
      name: database-credentials
Why Sealed Secrets is Perfect for GitOps #
Sealed Secrets aligns perfectly with GitOps principles:
- Everything in Git: Both application config and secrets are version controlled
 - Declarative: SealedSecrets are Kubernetes resources like any other
 - Immutable: Changes require creating new SealedSecrets (proper audit trail)
 - Secure: Only the target cluster can decrypt the secrets
 - Simple: No external dependencies or complex PKI infrastructure
 
The controller automatically handles the lifecycle - when you update a SealedSecret in Git, Flux detects the change and applies it, the controller decrypts it and updates the corresponding Secret.
Key Concepts #
SealedSecret Resource #
A SealedSecret is a Kubernetes Custom Resource that contains encrypted data:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: my-secret
  namespace: default
spec:
  encryptedData:
    key1: encrypted_value_1
    key2: encrypted_value_2
  template:
    metadata:
      name: my-secret
      labels:
        app: myapp
Scoping #
Sealed Secrets supports different scoping levels:
- strict (default): Secret can only be unsealed in the same namespace and with the same name
 - namespace-wide: Secret can be unsealed in the same namespace with any name
 - cluster-wide: Secret can be unsealed in any namespace with any name
 
Key Rotation #
The controller automatically handles key rotation to maintain security while ensuring existing SealedSecrets continue to work.
Exercises #
Install Sealed Secrets Controller with Flux #
Objective: Install the Sealed Secrets controller in your cluster using Flux to manage it via GitOps.
Steps:
- Create a new directory in your GitOps repository for sealed secrets:
mkdir -p clusters/c0x/sealed-secrets - Download the sealed-secrets installation manifest from the releases page and place it in the directory
 - Commit and push the files to your GitOps repository
 - Verify the controller is installed:
kubectl get pods -n kube-system | grep sealed-secrets 
Expected Result: The sealed-secrets-controller pod should be running in the kube-system namespace.
Create and Deploy a Sealed Secret #
Objective: Create a sealed secret for a database connection and deploy it using GitOps.
Steps:
- 
If you haven’t installed the
kubesealtool already, head over to the essential tools article for installation instructions. - 
Create a regular Secret manifest
# temp-secret.yaml apiVersion: v1 kind: Secret metadata: name: database-credentials namespace: default data: username: YWRtaW4= # admin password: UGFzc3dvcmQxMjM= # Password123β οΈ This file should not be committed to your git repo. Never commit a
Secretto your git repository. 
- Encrypt the secret using 
kubeseal:kubeseal -f temp-secret.yaml -w clusters/c0x/database-sealed-secret.yaml - Delete the temporary unencrypted file:
rm temp-secret.yaml - Examine the generated 
SealedSecret:cat clusters/c0x/database-sealed-secret.yaml - Commit and push the 
SealedSecretto your repository - Verify the secret was created in your cluster:
kubectl get sealedsecrets kubectl get secrets kubectl get secret database-credentials -o yaml 
Expected Result:
- A SealedSecret should exist in your cluster
 - A corresponding Secret should be automatically created
 - The Secret should contain the correct decrypted values
 
Troubleshooting #
Common commands for debugging Sealed Secrets issues:
# Check controller status
kubectl get pods -n kube-system | grep sealed-secrets
kubectl logs -n kube-system -l name=sealed-secrets-controller
# Verify SealedSecret resources
kubectl get sealedsecrets
kubectl describe sealedsecret <name>
# Check if secrets are being created
kubectl get secrets
kubectl describe secret <name>
# Test encryption manually
kubeseal --fetch-cert > cert.pem
echo -n "test" | kubeseal --raw --from-file=/dev/stdin --name myname --namespace default