Skip to content
LatchkeyLatchkey home

kubeseal: Encrypt a Secret into a SealedSecret

kubeseal encrypts a regular Kubernetes Secret into a SealedSecret that only the cluster controller can decrypt, so it is safe to commit to Git.

Sealed Secrets let you keep secrets in a GitOps repo. kubeseal takes a plain Secret and produces an encrypted SealedSecret resource.

What it does

kubeseal reads a Secret manifest, fetches the sealed-secrets controller public certificate, and encrypts each value with it, emitting a SealedSecret. Only the controller in that cluster holds the private key, so the output is safe to store in Git. By default sealing is scoped to the Secret's name and namespace.

Common usage

Terminal
kubectl create secret generic db \
  --from-literal=password="$DB_PASSWORD" \
  --dry-run=client -o yaml \
  | kubeseal --format yaml > sealedsecret.yaml

Options

FlagWhat it does
--format yaml|jsonOutput format of the SealedSecret
--scope strict|namespace-wide|cluster-wideHow tightly the seal is bound to name/namespace
--controller-name <name>Controller deployment name (default sealed-secrets-controller)
--controller-namespace <ns>Namespace of the controller (default kube-system)
--cert <file>Use an offline public cert instead of querying the cluster

In CI

A SealedSecret sealed with the default strict scope only decrypts when applied under the exact name and namespace it was sealed for; renaming it later breaks decryption. For a secret reused across namespaces, seal with --scope cluster-wide so the controller does not enforce the namespace.

Common errors in CI

"error: cannot fetch certificate: ... the server could not find the requested resource" means the --controller-name or --controller-namespace is wrong. "cannot get sealed secret service" means kubeseal cannot reach the controller; use --cert with a fetched certificate for offline sealing. Sealing succeeds but the controller logs "no key could decrypt secret" when the SealedSecret's name/namespace differ from where it was sealed.

Using this in CI

A runner has no kubeconfig, no cached context, and no interactive auth. Every kubectl invocation in CI needs the context supplied explicitly, and most confusing CI failures here are the command running against the wrong cluster or no cluster at all.

Terminal
# never rely on the ambient context on a runner
kubectl --context "$KUBE_CONTEXT" -n "$NAMESPACE" get pods

# confirm what you are actually connected to before mutating anything
kubectl config current-context
kubectl cluster-info

# fail fast instead of hanging on an unreachable API server
kubectl --request-timeout=30s get nodes

Frequently asked questions

kubeseal: Encrypt a Secret into a SealedSecret?
Sealed Secrets let you keep secrets in a GitOps repo. kubeseal takes a plain Secret and produces an encrypted SealedSecret resource.
What it does?
kubeseal reads a Secret manifest, fetches the sealed-secrets controller public certificate, and encrypts each value with it, emitting a SealedSecret. Only the controller in that cluster holds the private key, so the output is safe to store in Git. By default sealing is scoped to the Secret's name and namespace.
In CI?
A SealedSecret sealed with the default strict scope only decrypts when applied under the exact name and namespace it was sealed for; renaming it later breaks decryption. For a secret reused across namespaces, seal with --scope cluster-wide so the controller does not enforce the namespace.
Common errors in CI?
"error: cannot fetch certificate: ... the server could not find the requested resource" means the --controller-name or --controller-namespace is wrong. "cannot get sealed secret service" means kubeseal cannot reach the controller; use --cert with a fetched certificate for offline sealing.

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card