kubectl or a YAML manifest. A core GitOps principle is that every resource—including Secrets—should be stored declaratively in Git. That raises a key question: how do you keep sensitive (often Base64-encoded) data safe in a Git repository (public or private)?
There are multiple approaches (HashiCorp Vault, SOPS, Bitnami Sealed Secrets, etc.). This article focuses on Bitnami Sealed Secrets and explains how to create, store, and deploy encrypted Secret manifests that only the target cluster can decrypt.
Overview
- The Bitnami Sealed Secrets controller is a Kubernetes controller installed into your cluster.
- The
kubesealclient transforms a standard Kubernetes Secret manifest into a SealedSecret object that is safe to commit to Git because the secret data is encrypted. - The controller running in the target cluster holds the private key and decrypts SealedSecret objects back into native Kubernetes Secrets.
- Only the controller (or anyone with access to its private key) can decrypt a SealedSecret. Even the author who created the SealedSecret cannot recover plaintext without the controller’s private key.
- You can install the controller with Helm, Kustomize, or via GitOps tools such as ArgoCD. The examples below show both Helm/GitOps options.
How sealing and unsealing works
- On the client side you use the
kubesealutility to convert a native Secret YAML into a SealedSecret YAML. kubesealuses asymmetric cryptography: the Sealed Secrets controller in the cluster holds the private key andkubesealuses the controller’s public certificate to encrypt the Secret so that only the controller can decrypt it.- By default
kubesealfetches the controller’s public certificate automatically over the Kubernetes API. You can also provide the certificate locally with--cert(useful when the cluster API is not reachable or for reproducible encryption).
Quick reference: common commands
| Step | Command / Example | Purpose |
|---|---|---|
| Create a local Secret manifest (dry-run) | kubectl create secret generic mysql-password --from-literal=password='s1DdH@rtf' --dry-run=client -o yaml > mysql-password_k8s-secret.yaml | Export a standard Secret to YAML without applying it to the cluster. |
| Create ArgoCD app (example) | argocd app create sealed-secrets \ --repo https://bitnami-labs.github.io/sealed-secrets \ --helm-chart sealed-secrets \ --revision 2.2.0 \ --dest-namespace kube-system \ --dest-server https://1.2.3.4 | Example: use ArgoCD to install the Sealed Secrets Helm chart into a cluster. |
| Install kubeseal client | wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/kubeseal-0.18.0-linux-amd64 -O kubeseal && \ sudo install -m 755 kubeseal /usr/local/bin/kubeseal | Download and install the kubeseal binary locally. |
| Create SealedSecret YAML | kubeseal -o yaml --scope cluster-wide --cert sealedSecret.crt < mysql-password_k8s-secret.yaml > mysql-password_sealed-secret.yaml | Encrypt the Secret YAML to produce a SealedSecret suitable for committing to Git. |
Example workflow and commands (detailed)
- Create a Kubernetes Secret manifest locally (dry-run) and export it to YAML:
- (Optional) Create an ArgoCD application to deploy the Sealed Secrets Helm chart into your cluster (example):
- Install the
kubesealclient locally (example using a release binary):
- Create the SealedSecret YAML by running
kubeseal. Ifkubesealcan reach the Sealed Secrets controller, it will fetch the controller’s public certificate automatically. You can also provide the certificate file using--cert:
If you omit
--cert, kubeseal will try to fetch the controller’s public certificate from the cluster. Use --cert when you cannot reach the cluster API from your local environment or when you want to ensure reproducible encryption using a specific certificate.-
Commit the generated
mysql-password_sealed-secret.yamlto your Git repository. Any GitOps operator (ArgoCD, Flux, etc.) can sync the manifest to the cluster. - At deploy time the Sealed Secrets controller running in the target cluster decrypts the SealedSecret and creates a native Kubernetes Secret in the cluster namespace. From the application pod’s perspective, this is a normal Secret and is consumed in the usual way (environment variables, volumes, etc.).
Why this is secure
- SealedSecret objects are encrypted using the controller’s public key; only the controller’s private key (stored inside the cluster) can decrypt them.
- Storing SealedSecrets in Git is safe—even in public repositories—because the ciphertext cannot be converted back to plaintext without the controller’s private key.
- Application pods are unaware of the encryption step: they receive native Kubernetes Secrets at runtime exactly as they would with any conventional Secret.
Best practices
- Keep the Sealed Secrets controller private key secure and limit access to the cluster control plane.
- Use
--certfor reproducible encryption when working from CI/CD runners that cannot access the cluster API. - Rotate the controller keys periodically and plan for re-sealing Secrets if keys are rotated or compromised.
- Use RBAC and network policies to limit who can create SealedSecrets and who can access Secrets in the cluster.
Summary
Bitnami Sealed Secrets offers a simple, Git-friendly method for managing Kubernetes Secrets using asymmetric encryption. Usekubeseal to produce SealedSecret manifests, store those manifests in Git, and let your GitOps operator and the Sealed Secrets controller handle decryption and provisioning within the cluster.
Links and references
- Bitnami Sealed Secrets GitHub: https://github.com/bitnami-labs/sealed-secrets
- kubeseal releases: https://github.com/bitnami-labs/sealed-secrets/releases
- ArgoCD: https://argo-cd.readthedocs.io/
- Kubernetes Secrets: https://kubernetes.io/docs/concepts/configuration/secret/
- SOPS: https://github.com/mozilla/sops
- HashiCorp Vault: https://www.vaultproject.io/