Skip to main content

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 SealedSecret lives 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 with kubectl.
  • kubeseal installed 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 the SealedSecret goes into Git. Add secret.yaml to your .gitignore for 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 SealedSecret objects 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.
Certificate rotation

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