When working seriously with Helm in production environments, one of the less-discussed but highly impactful topics is how Helm stores and manages release state. This is where Helm drivers come into play. Understanding Helm drivers is not just an academic exercise; it directly affects security, scalability, troubleshooting, and even disaster recovery strategies.
A Helm driver defines the backend storage mechanism Helm uses to persist release information such as manifests, values, and revision history. Every Helm release has state, and that state must live somewhere. The driver determines where and how this data is stored.
Helm drivers are configured using the HELM_DRIVER environment variable. If the variable is not explicitly set, Helm defaults to using Kubernetes Secrets.
export HELM_DRIVER=secrets
This simple configuration choice can have deep operational consequences, especially in regulated environments or large-scale clusters.
Available Helm Drivers
Secrets Driver (Default)
The secrets driver stores release information as Kubernetes Secrets in the target namespace. This has been the default driver since Helm 3 was introduced.
Secrets are base64-encoded and can be encrypted at rest if Kubernetes encryption at rest is enabled. This makes the driver suitable for clusters with moderate security requirements without additional configuration.
ConfigMaps Driver
The configmapsdriver stores Helm release state as Kubernetes ConfigMaps. Functionally, it behaves very similarly to the secrets driver but without any form of implicit confidentiality.
export HELM_DRIVER=configmaps
This driver is often used in development or troubleshooting scenarios where human readability is preferred.
Memory Driver
The memory driver stores release information only in memory. Once the Helm process exits, all state is lost.
export HELM_DRIVER=memory
This driver is rarely used outside of testing, CI pipelines, or ephemeral validation workflows.
Evolution of Helm Drivers
Helm drivers were significantly reworked with the release of Helm 3 in late 2019. Helm 2 relied on Tiller and ConfigMaps by default, which introduced security and operational complexity. Helm 3 removed Tiller entirely and introduced pluggable storage backends with Secrets as the secure default.
Since then, improvements have focused on performance, stability, and better error handling rather than introducing new drivers. The core abstraction has remained intentionally small to avoid fragmentation.
Practical Use Cases and When to Use Each Driver
In production Kubernetes clusters, the secrets driver is almost always the right choice. It integrates naturally with RBAC, supports encryption at rest, and aligns with Kubernetes-native security models.
ConfigMaps can be useful when debugging failed upgrades or learning Helm internals, as the stored data is easier to inspect. However, it should be avoided in environments handling sensitive values.
The memory driver shines in CI/CD pipelines where chart validation or rendering is needed without polluting a cluster with state.
Practical Examples
Switching drivers dynamically can be useful when inspecting a release:
HELM_DRIVER=configmaps helm get manifest my-release
Or running a dry validation in CI:
HELM_DRIVER=memory helm upgrade --install test ./chart --dry-run
Final Thoughts
Helm drivers are rarely discussed, yet they influence how reliable, secure, and observable your Helm workflows are. Treating the choice of driver as a deliberate architectural decision rather than a default setting is one of those small details that differentiate mature DevOps practices from ad-hoc automation.
If you have ever built a Helm chart that includes configuration files, scripts, or property files inside a ConfigMap or Secret, you have probably hit the same wall: the default templating engine only processes YAML files inside the templates/ directory. Everything else is treated as static content.
This is a problem because real-world applications rarely deploy with hardcoded configuration. You need environment-specific values in your .properties files, tokens in your JSON configs, or dynamic hostnames in your shell scripts. Helm provides three core functions to handle this: .Files.Get, .Files.Glob, and tpl. Each solves a different piece of the puzzle, and combining them is where things get powerful.
This guide covers every practical pattern you will need, from the simplest .Files.Get call to the advanced tpl + .Files.Glob combination that gives you full templating inside external files. For a broader look at Helm packaging, see our definitive Helm package management guide.
Understanding the Helm Chart File Structure
Before diving into the functions, it is important to understand what Helm considers a “file” and where it can access them. A typical chart looks like this:
Files inside templates/ go through the full Helm template engine. Files outside it (like config/app.properties or files/zones.json) are accessible via the .Files object, but they are not templated automatically. This is the key distinction that catches most people off guard.
.Files.Get: Reading a Single File
The .Files.Get function reads the content of a specific file by its path, relative to the chart root. This is the simplest way to include file content in a ConfigMap or Secret.
This reads config/app.properties from the chart root and injects it into the ConfigMap, preserving the original content. The indent 4 ensures correct YAML indentation.
Secret Example with .Files.Get and b64enc
For Secrets, Kubernetes expects base64-encoded values in the data field. Combine .Files.Get with b64enc:
.Files.Get Path Limitations: Why “../” Does Not Work
A very common question is whether you can use .Files.Get to access files outside the chart directory, for example .Files.Get "../shared/config.yaml". The answer is no. Helm restricts file access to the chart root for security reasons. Any path that tries to escape the chart directory with ../ will silently return an empty string.
If you need to share files between charts, the recommended patterns are:
Copy shared files into each chart during your CI/CD pipeline before packaging
Pass the content through values.yaml using a parent chart
Also note that files inside the templates/ directory and Chart.yaml itself are not accessible through .Files. Only files that are packaged with the chart and not in templates/ can be read.
.Files.Glob: Working with Multiple Files
When you have multiple configuration files to include, .Files.Glob lets you match files using glob patterns and iterate over them. This is especially useful when your chart ships with several config files that all need to end up in the same ConfigMap.
.Files.Glob with .AsConfig Example: ConfigMap from Multiple Files
The .AsConfig helper takes a set of matched files and formats them as ConfigMap data entries, where each filename becomes the key and the file content becomes the value:
Notice two important details here. First, base $path extracts just the filename (e.g., init.sh) from the full path (scripts/init.sh). Second, inside a range loop the context changes, so you must use $. (dollar-dot) to access the root scope when calling $.Files.Get.
The tpl Function: Full Templating Inside External Files
This is where things get really interesting. The .Files.Get and .Files.Glob functions read file content as-is. If your app.properties file contains {{ .Values.database.host }}, it will be included literally as that string, not replaced with the actual value.
The tpl function solves this by passing a string through the Helm template engine. When you combine tpl with .Files.Get, your external files get the same templating power as files inside templates/.
tpl + .Files.Get: Templated ConfigMap
Consider this config/app.properties file in your chart:
The tpl function takes two arguments: the string to process and the context (.). It runs the content through the template engine, replacing all {{ .Values.* }} references with actual values. The result is a fully dynamic configuration file.
tpl + .Files.Glob: Templating Multiple Files
To template every file matched by a glob pattern, combine the range iteration with tpl:
This iterates over all .json files in the secrets/ directory, passes each through the template engine (so {{ .Values.* }} references are resolved), base64-encodes the result, and includes it in the Secret. This is the most powerful pattern for managing multiple dynamic configuration files.
Common Pitfalls and Troubleshooting
Working with .Files in Helm can produce confusing errors or silent failures. Here are the most common issues and how to fix them.
.Files.Get Returns Empty String
If .Files.Get returns nothing, check these three things:
Wrong path: The path is relative to the chart root, not to the templates directory. Use .Files.Get "config/app.properties", not .Files.Get "../config/app.properties".
File excluded by .helmignore: Check your .helmignore file. If it matches the file’s path, the file will not be packaged with the chart and .Files.Get will return empty.
File in templates/ directory: Files inside templates/ are not accessible via .Files. Move them to a different directory.
YAML Indentation Errors
The most frequent rendering error is incorrect indentation. Always use indent N (or nindent N) when including file content in YAML. The difference between indent and nindent is that nindent adds a newline before the content, which is cleaner when using {{- to trim whitespace:
# Using indent (requires | on the previous line)
data:
app.properties: |
{{ .Files.Get "config/app.properties" | indent 4 }}
# Using nindent (cleaner, self-contained)
data:
app.properties: {{ .Files.Get "config/app.properties" | nindent 4 }}
Chart Size Limit
Helm charts stored in Kubernetes as Secrets or ConfigMaps are subject to the 1 MB limit imposed by etcd. If your chart includes many large files, you may hit this limit during helm install. The error typically reads release: invalid or etcd: request is too large. In that case, consider mounting files via persistent volumes or external config management instead of embedding them in the chart.
Context Issues Inside range Loops
Inside a range block, . refers to the current iteration item, not the root context. This means .Files.Get will fail. Use $. to access the root context:
# Wrong: . is the loop item, not the root context
{{- range $path, $_ := .Files.Glob "config/*" }}
{{ .Files.Get $path }} {{/* This will fail */}}
{{- end }}
# Correct: use $ to access root context
{{- range $path, $_ := .Files.Glob "config/*" }}
{{ $.Files.Get $path }} {{/* This works */}}
{{- end }}
This way, every file in config/ is automatically included, templated, and properly formatted. Adding a new configuration file is as simple as dropping it into the directory. No template changes required.
Quick Reference: .Files Functions Summary
Here is a quick reference of all file-related functions available in Helm:
Function
What it does
Example
.Files.Get
Returns the content of a single file as a string
.Files.Get "config/app.yaml"
.Files.Glob
Returns all files matching a glob pattern
.Files.Glob "config/*.json"
.AsConfig
Formats matched files as ConfigMap data entries
(.Files.Glob "config/*").AsConfig
.AsSecrets
Formats matched files as base64-encoded Secret data
(.Files.Glob "secrets/*").AsSecrets
tpl
Passes a string through the Helm template engine
tpl (.Files.Get "f.yaml") .
.Files.Lines
Returns file content as a list of lines
.Files.Lines "config/hosts.txt"
.Files.AsBytes
Returns file content as a byte array
.Files.Get "bin/tool" \| b64enc
Conclusion
Helm file handling follows a clear escalation path depending on what you need. Use .Files.Get when you need a single file as-is. Use .Files.Glob with .AsConfig or .AsSecrets when you have multiple files that need no modification. And use tpl combined with either function when your files need dynamic values from values.yaml.
The most common mistake is forgetting that .Files only reads files — it does not template them. The moment you need {{ .Values.* }} inside an external file, tpl is the function you are looking for. For more Helm patterns and advanced tips, explore our guides on Helm hooks, Helm loops, and Helm dependencies.
Frequently Asked Questions
Can .Files.Get access files outside the chart directory?
No. Helm restricts .Files.Get to files within the chart root directory. Paths containing ../ will silently return an empty string. This is a security constraint to prevent charts from reading arbitrary files from the filesystem. If you need shared files across charts, use library charts, copy files during CI/CD, or pass content through parent chart values.
What is the difference between .AsConfig and .AsSecrets in Helm?
.AsConfig formats files as plain text ConfigMap data entries (key: filename, value: file content). .AsSecrets does the same but automatically base64-encodes each file’s content, which is required for the data field in Kubernetes Secret resources. Both are called on the result of .Files.Glob.
How do I use Helm values inside a properties file or JSON config?
Use the tpl function. Instead of .Files.Get "config/file.json", use tpl (.Files.Get "config/file.json") .. This passes the file content through the Helm template engine, so any {{ .Values.* }} references in the file will be resolved against your chart’s values.
Why does .Files.Get return an empty string?
Three common causes: the file path is wrong (paths are relative to the chart root, not the templates directory), the file is excluded by .helmignore, or the file is inside the templates/ directory which is not accessible via .Files. Run helm template locally and check the output to debug.
Is there a size limit for files included via .Files.Get?
Helm itself does not impose a file size limit, but the packaged chart is stored as a Kubernetes Secret or ConfigMap which is subject to the etcd 1 MB size limit. If your chart with all its files exceeds this, helm install will fail. For large files, consider external config management or persistent volumes instead of embedding them in the chart.