Encrypted secrets with SealedSecrets
A Kubernetes Secret is not encrypted. Its content is base64-encoded, which decodes in a single command. Committing it to Git means publishing your passwords in plain text to anyone with access to the repository.
Your hosted-* namespaces let you manage your secrets on your own, without going through our team. The catch: they must be encrypted before they touch your repository. That is what SealedSecrets is for.
Why SealedSecrets?
SealedSecrets (Bitnami) is built on asymmetric encryption. You seal a secret with a public certificate that anyone can hold. Only the controller installed in the cluster can decrypt it, using a private key that never leaves the cluster.
Sealing produces a SealedSecret: an encrypted file, safe to commit to Git. Once applied, the controller decrypts it and creates a standard Kubernetes Secret that your applications consume as usual.
The key point: encryption is bound to the namespace (strict mode, the default). A SealedSecret sealed for hosted-yourorg-app-prod decrypts only in that namespace. Copied anywhere else, it fails. That is what makes the object safe in Git and impossible to reuse against another namespace.
What it changes for your hosted-* namespaces
Adding a secret used to mean opening a ticket and waiting for the ops team. Now your organization's service account is allowed to create, update and delete SealedSecret objects within your own hosted-* namespaces, and nowhere else.
In practice:
- You are autonomous. You seal, you commit, you apply. No middleman.
- Your secrets stay isolated. You cannot decrypt, or even read, another organization's secrets. The private key stays in
kube-system, out of your reach. - Your repository becomes the source of truth. The
SealedSecretlives in Git alongside your manifests. Your secrets follow the same GitOps flow as the rest of your deployment.
Prerequisites
- Your
hosted-*kubeconfig(the one we provided) configured withkubectl. kubesealinstalled locally:
# macOS
brew install kubeseal
# Linux (adjust the version)
curl -sL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.31.0/kubeseal-0.31.0-linux-amd64.tar.gz" \
| tar -xz kubeseal && sudo install kubeseal /usr/local/bin/
Steps
1. Fetch the public certificate
The public certificate is what you seal with. Fetch it once and keep it:
kubeseal --controller-name=sealed-secrets --controller-namespace=kube-system \
--fetch-cert > sealed-secrets.pem
2. Write the plain-text secret locally
Generate the Secret manifest without ever applying it to the cluster (--dry-run=client):
kubectl create secret generic app-credentials \
--namespace hosted-yourorg-app-prod \
--from-literal=DATABASE_PASSWORD='your-password' \
--dry-run=client -o yaml > secret.yaml
This file holds your secret in plain text. It must never be committed. Delete it at the end.
3. Seal it
kubeseal --cert sealed-secrets.pem \
--namespace hosted-yourorg-app-prod \
--format yaml < secret.yaml > sealed-secret.yaml
sealed-secret.yaml holds an encrypted SealedSecret. The namespace is locked into the encryption: the --namespace passed here decides where the object can be decrypted.
4. Commit
rm secret.yaml # never keep the plain text
git add sealed-secret.yaml
git commit -m "chore: add app database credentials"
5. Apply
kubectl apply -f sealed-secret.yaml
The controller detects the SealedSecret, decrypts it and creates the matching Kubernetes Secret in your namespace.
Verification
# The SealedSecret is present
kubectl get sealedsecret -n hosted-yourorg-app-prod
# The controller created the standard Secret from it
kubectl get secret app-credentials -n hosted-yourorg-app-prod
Your pods then consume that Secret like any other, through envFrom, valueFrom.secretKeyRef or a mounted volume.
Best practices
- Never commit the plain-text
Secret. Only theSealedSecretgoes into Git. Addsecret.yamlto your.gitignorefor safety. - A sealing is bound to a namespace AND a name. To rename a secret or move it to another namespace, re-seal it from the plain text. You do not "move" a
SealedSecret. - The private key and its backups are managed by Bunker. You have nothing to back up on that side. If the cluster is restored, your
SealedSecretobjects become decryptable again with no action on your part. - Rotate your secrets as usual: re-seal the new value, commit, apply. The controller updates the
Secret.
The public certificate changes when the cluster rotates its keys. If a sealing fails with a certificate error, re-run step 1 to fetch the current certificate.
Next steps
- Authentication to understand how your access to the platform is secured
- GitLab CI on Kubernetes to apply your
SealedSecretobjects from a pipeline - Vaultwarden Password Manager to store secrets on the team side