Helm Under The Hood: Storage Using Secrets

Helm v3 uses Kubernetes Secrets as the default method to store release information in a cluster. This bucks some newer trends, but if you look at the needs and features it turns out that Secrets are a good fit. In this post you’ll learn why Helm uses Secrets by default and how you can do something similar if Secrets fit well for you.

Why Secrets?

Custom Resources based on Custom Resource Definitions (CRDs) are common in Kubernetes these days. They provide a means to extend the Kubernetes API. This is great and very powerful.

When Helm v3 work began we looked at using CRDs. This is what I expected would be used. But, when Helm maintainers talked with Helm users and potential users we learned that there are many who can’t install CRDs into clusters (no permission). Using CRDs would limit the potential user base for Helm. We wanted an option that would enable a broader user base.

When we looked at other options we found that Secrets provide many useful features including:

  • Secrets can be typed. If a type isn’t set it’s Opaque.
  • You can use encrypted storage for Secrets.
  • Most users who are going to install applications have permission to use Secrets.

Secrets do lack validation on the data model (unless you use a custom ValidatingAdmissionWebhook). Since Helm performs the CRUD operations on these objects rather than end users this lack of validation isn’t an issue.

Using Secrets

In Helm v3 the secrets Helm uses for its storage are typed. The type is helm.sh/release.v1. The type uses a DNS prefix for the Helm project. While not required, this follows the prefix guidance for labels and annotations. The object type is release and the version is v1. This provides the ability for Helm to store different types and versions without having collisions with other projects.

Different types are useful to use. For example, you can query for secrets based on type in kubectl:

$ kubectl get secrets --field-selector "type=helm.sh/release.v1"

The data is stored on the release object in a repeatable manner. Internally to Helm there is a Release object. The instance is converted to JSON, gzipped, and base64 encoded. When data is retrieved this process is undone to restore the Release object in memory.

You might be wondering, why is the release stored this way? This process shrinks down the overall data stored. etcd, the database used for storing Kubernetes objects, has size constraints. We want to avoid hitting those. These steps came out of experience hitting them in the past with some edge cases of far more complex charts than is typical.

The names of Secrets are not unique per type. They are unique per namespace. To work around issues of secrets with the same name but different types, Helm has a naming pattern for its release secrets that is sh.helm.release.v1.[instance name].[release version]. You’ll notice the use of reverse DNS notation for Helms domain followed by the type and type version. This was done due to collisions when releases as Secrets in Helm v2 had names that collided with releases as Secrets in Helm v3. It also aids in Secrets created by charts not colliding with the Secrets Helm uses.


If you don’t need the Kubernetes API extended for user interaction, Secrets are a good way for simple storage. Their ability to be typed, security capabilities, wide access, and flexibility make them useful for many situations.