# Sealed Secrets turns Kubernetes Secrets into a git commit you can actually read

> A Bitnami controller and the kubeseal CLI encrypt Secret values one way, so the encrypted form can live in a public repository while only the cluster holding the private key can unseal it.

**bitnami/sealed-secrets** — A Kubernetes controller and tool for one-way encrypted Secrets

- Repository: https://github.com/bitnami/sealed-secrets
- Stars: 9,299 · Forks: 779
- Language: Go
- License: Apache-2.0
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/bitnami-sealed-secrets

## The problem is stated in one quoted line at the top of the README

The README opens with a problem and a solution, and they are worth reading closely because the solution is narrower than the headline suggests. The problem is quoted from someone who has clearly done this before: I can manage all my K8s config in git, except Secrets.

The solution is to encrypt your Secret into a SealedSecret, which is described as safe to store even inside a public repository. The SealedSecret can be decrypted only by the controller running in the target cluster, and the README is explicit that nobody else, not even the original author, is able to obtain the original Secret from the SealedSecret.

That last clause is the design decision everything else follows from. The person who ran `kubeseal` cannot decrypt the output. There is no symmetric key sitting in a CI secret store, no envelope encryption scheme where a KMS key unwraps the data for whoever asks, and no recovery path for the author. The decryption capability lives in exactly one place, inside the cluster, and it stays there.

The project splits into two halves that live in different places. The cluster-side half is a controller, described as an operator, which watches for SealedSecret resources and unseals them into ordinary Secrets. The client-side half is a utility named `kubeseal`, which uses asymmetric crypto to encrypt values that only the controller can decrypt. This split is why the tool fits GitOps: the developer-side binary runs anywhere, including a laptop or a CI job, and never needs cluster credentials to encrypt. Only the controller needs to be trusted, and it needs to be trusted anyway since it creates the Secrets.

## A SealedSecret is a recipe, and the plaintext Secret looks completely ordinary

The README describes a SealedSecret resource as a recipe for creating a secret. The example is deliberately minimal, one key with a truncated ciphertext:

```yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: mysecret
  namespace: mynamespace
spec:
  encryptedData:
    foo: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....
```

Once unsealed, that produces a Secret equivalent to this:

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
  namespace: mynamespace
data:
  foo: YmFy  # <- base64 encoded "bar"
```

That second block is worth dwelling on, because it is the whole point of the project. The resulting resource is not a special sealed type that pods need a custom admission webhook or a sidecar to read. It is a plain Kubernetes Secret with the standard `v1` apiVersion, and the README says it appears in the cluster after a few seconds and can be referenced from a `Pod` the same way as a Secret you created by hand. Nothing in your workload manifests changes. That property is what allows an existing cluster to adopt Sealed Secrets incrementally, one Secret at a time, with no coordinated migration.

The relationship between the custom resource and the Secret it produces is compared in the README to the familiar Deployment versus Pod relationship, with an important caveat attached: annotations and labels on the SealedSecret are not the same as annotations on the generated Secret. To express that distinction, the SealedSecret has a separate `template` section that encodes the fields the controller should place on the generated Secret. Anyone who assumes `kubectl apply` of a SealedSecret gives them the same metadata behaviour they would get from a Deployment needs to read that section.

The controller is also careful about ownership, and the usage documentation reflects that. There is a documented flag for sealing a Secret which skips setting owner references, plus modes for patching an existing Secret rather than creating one, for updating an existing Secret in place, an experimental raw mode, and a validation command for checking a SealedSecret before committing it.

## Scopes are the part people get wrong, and the binding is what makes this safe

Overwhelmingly one-way encryption is the wrong mental model, and the README corrects it in a specific way. A ciphertext produced by `kubeseal` is not a general-purpose encrypted blob. It is bound to the controller's public key, and further bound to properties of the target Secret, including its name and namespace.

The README also documents the option of bringing your own pre-generated certificates rather than letting the controller generate a key pair on first run. That matters for organisations that already have a key management story, because the controller can then consume a certificate they generated and backed up themselves.

The consequence of this binding is worth stating plainly, because it is what makes the public repository claim true rather than marketing. If someone copies a SealedSecret from your repository into a different cluster, that cluster cannot unseal it, because the ciphertext was encrypted to a key that only your controller holds. The attack that a public repository normally enables, where an attacker reads the plaintext credential, does not apply here. What an attacker with repository access *can* do is delete or overwrite your SealedSecret, or replay an old one, which is a GitOps integrity problem rather than a disclosure problem, and it is handled by the same review process that covers the rest of your manifests.

One more thing the binding does: it prevents ciphertext from being repurposed. A value encrypted for a specific Secret name and namespace will not decrypt as part of a different object. That is a meaningful improvement over naive envelope encryption, where any ciphertext that reaches a decryptor is decrypted, and it is the reason the project describes itself as safe to store in a public repository rather than merely convenient.

The cluster-side story is similarly configurable rather than one size. The README documents installing the controller with Kustomize and with a Helm chart, running in restricted environments with no RBAC, running one controller for a subset of namespaces, running the controller outside the `kube-system` namespace and telling `kubeseal` how to find it, managing SealedSecrets across the cluster or per namespace, verifying the container images, and configuring the controller's unseal retries.

## Key rotation, and the misconception the documentation bothers to name

A one-way encryption scheme in a cluster is only as good as its key lifecycle, and the Secret Rotation section is the longest part of this README for good reason. It covers sealing key renewal, the key registry initialisation priority order, user secret rotation, early key renewal, manual key management as an advanced topic, and re-encryption as another advanced topic.

The structural point is that there are two different keys with two different jobs. The sealing key is the private key the controller holds, and it cannot decrypt anything on its own; it can only sign, which is why nobody else, including you, can unseal from the ciphertext. Renewal adds a new key to the registry rather than replacing the old one, and the controller keeps the earlier keys around so that SealedSecrets sealed years ago still unseal after a renewal.

The consequence, which the documentation states plainly, is that renewal does not retroactively protect anything you already sealed. Old ciphertext stays readable by the controller as long as the old key is in the registry. To actually change what an existing SealedSecret protects, you re-encrypt it, which means running `kubeseal` again against the current certificate and committing the new ciphertext. There is a dedicated subsection on common misconceptions about key renewal for exactly this reason, and it is a sign of a project that has fielded this question many times.

Manual key management is offered for teams that need to control the registry directly, and the FAQ adds the recovery cases. There is a documented procedure for decrypting secrets offline with a backup key, and a note on how to back up SealedSecrets. The existence of an offline decryption path with a backup key is the one place where the strict separation breaks down by design, and it is worth understanding exactly what that backup key is before deciding to use it.

Release cadence supports taking this seriously. v0.40.0 shipped on 2026-09-10, following v0.39.1 on 2026-08-20, with the Helm chart released as a separate helm-v2.20.0 tag on the same day as the controller release. The v0.40.0 changelog is mostly maintenance, including a bump of Go to 1.26.8, which matches the go.mod directive and the current Kubernetes library set at v0.37.0.

## Installing it: pick the path that matches your cluster's permissions

The README offers three controller paths and several client paths, and the choice between them is mostly a question about what your cluster permits.

For the controller, the primary route is the Helm chart, and there is a documented variant of it for restricted environments. The Kustomize route uses the jsonnet sources in the repository: `controller.jsonnet` is the standard manifest set, `controller-norbac.jsonnet` is the variant for environments where you cannot create the RBAC the controller wants, and `controller-podmonitor.jsonnet` adds a PodMonitor so the controller's own metrics are scraped. The `versions.env` file, `jsonnetfile.json` and `kube-fixes.libsonnet` alongside them suggest a jsonnet-first upstream workflow with Helm and Carvel distributions generated from it, and `schema-v1alpha1.yaml` publishes the CRD schema for the `bitnami.com/v1alpha1` apiVersion.

For the client, `kubeseal` is available through Homebrew, MacPorts, Nixpkgs, direct Linux binaries, and installation from source. The FAQ adds practical details: what flags are available, and what to do when the controller is not running in `kube-system`, where you have to point `kubeseal` at the right service and certificate.

The upgrade path is documented as a section of its own, alongside supported versions and a compatibility matrix for Kubernetes versions. That matrix is the thing to check before pinning a chart version, since the controller talks to the API server through the Kubernetes client libraries and a cluster two major versions behind is a different conversation from one behind.

The FAQ also covers the everyday friction of GitOps at file level. There is a documented answer on encrypting multiple secrets in one YAML or JSON file, and another on updating specific parts of an encrypted JSON, YAML or TOML file, which is the workflow people hit as soon as they seal a whole config file rather than individual values. The answer involves scoping, which is why the Scopes section is not optional reading for anyone doing more than a handful of secrets.

## Where this fits against the other options

The alternative to Sealed Secrets is usually one of three things, and it helps to be clear about what each one trades away.

External secret managers such as Vault, AWS Secrets Manager or Google Secret Manager keep the plaintext out of git entirely and fetch it at runtime. That is a stronger model in one respect, since there is no ciphertext in the repository at all, and a weaker one in another: you now depend on a second system being reachable and correctly authorised during pod startup and during every reconciliation, which is a new failure mode precisely when something else is already going wrong. They also tend to need a chart or an operator of their own, so the deployment cost is similar.

SOPS with Age or GPG keeps files encrypted in git and decrypts through a sidecar or an init step. The difference is where decryption happens: SOPS decrypts at the workload boundary, which means the plaintext exists in a container and the original author can decrypt their own files. Sealed Secrets decrypts once in the controller and produces an ordinary Secret, so the plaintext exists once, in the API server, in the same place it would have if you had created it by hand.

Plain Secrets in git with base64 encoding is what people are trying to escape from, and the project's own problem statement is the honest version of the objection: base64 is not encryption, and a repository history is a permanent record.

What Sealed Secrets does not solve is worth saying too. A Secret in a cluster is base64 at rest unless encryption at rest is configured on the API server, and anyone with `get secrets` in the namespace can read the plaintext regardless of how it got there. Access control still matters. The project's claim is narrower and, as far as the documentation goes, actually true: the git repository does not contain the plaintext, and cannot be made to.

## Conclusion

Sealed Secrets has solved a genuinely annoying problem rather than a fashionable one, and the resolution is narrower than the marketing line suggests: the encrypted blob is bound to a specific cluster and a specific name and namespace, and everything else in the project exists to make that binding manageable. The documentation is unusually honest for a Bitnami project. It spells out the renewal model, lists the misconceptions people actually have about it, and explains that the sealing key cannot decrypt anything, only the controller can. At 9,286 stars, 779 forks, an Apache-2.0 license and a push on 2026-09-17, with the Go module pinned to Kubernetes libraries at v0.37.0 and a Helm chart released separately at helm-v2.20.0, it is a dependency people have already reasoned about carefully. The right way in is the Helm chart if you have RBAC, the jsonnet bundle in `controller.jsonnet` if you generate manifests as code, and `kubeseal --fetch-cert` before your first seal so the public certificate is under version control before any ciphertext exists.

## FAQ

### What are sealed secrets?

A SealedSecret is a custom resource in the bitnami.com/v1alpha1 apiVersion whose values are encrypted with asymmetric crypto so that only the Sealed Secrets controller running in the target cluster can decrypt them. The controller turns it into an ordinary v1 Secret that a Pod can reference the same way as any other. The author who ran kubeseal cannot decrypt it.

### Are sealed secrets safe?

The README states that a SealedSecret is safe to store even inside a public repository, because the ciphertext is encrypted to the target cluster's public key and bound to the Secret's name and namespace. Only the controller holding the private key can unseal it, so repository access alone does not reveal the plaintext. Note that this addresses disclosure, not deletion or overwrite, which stay subject to normal repository review.

### Do I lose my secrets if I lose access to the cluster?

Not necessarily. The README documents a procedure for decrypting secrets offline with a backup key, and separately explains how to back up your SealedSecrets. The key point to understand first is that the sealing key itself cannot decrypt anything, so a backup key is a distinct artifact from the one the controller uses to unseal.

### How do I install the controller and the kubeseal client?

The README documents the controller through a Helm chart or through Kustomize using the bundled jsonnet, including a no-RBAC variant for restricted environments. kubeseal is available via Homebrew, MacPorts, Nixpkgs, direct Linux binaries, or installation from source. It is worth fetching the controller certificate first, so the public key is under version control before any ciphertext exists.

## Sources

- [bitnami/sealed-secrets on GitHub](https://github.com/bitnami/sealed-secrets)
- [Issues](https://github.com/bitnami/sealed-secrets/issues)
- [License: Apache-2.0](https://github.com/bitnami/sealed-secrets/blob/main/LICENSE)
- [README](https://github.com/bitnami/sealed-secrets/blob/main/README.md)
- [Releases](https://github.com/bitnami/sealed-secrets/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bitnami-sealed-secrets
