The most surprising thing about Helm values.yaml files is that they’re not just configuration; they’re effectively a DSL for generating Kubernetes manifests.
Let’s say you’re deploying a Vector agent to your Kubernetes cluster using its official Helm chart. You’ve got a basic values.yaml to get started:
# values.yaml
image:
repository: vectorai/vector
tag: 0.33.0
serviceAccount:
create: true
name: vector-agent
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
config: |
[sources.my_source]
type = "host_metrics"
interval = 1
[sinks.my_sink]
type = "blackhole"
When you run helm install my-vector ./vector-chart -f values.yaml, Helm doesn’t just dump this into a file. It processes this through Go templating, injecting values into predefined templates within the chart’s templates/ directory. The {{ .Values.image.repository }} and {{ .Values.image.tag }} become vectorai/vector and 0.33.0 respectively, populating a Kubernetes Deployment manifest. The config block, with its triple-quoted string, is directly embedded into the Vector custom resource definition (CRD) that Vector uses.
The mental model here is that the Helm chart is a template engine, and your values.yaml provides the data that gets substituted into that engine. The chart’s templates/ directory contains Kubernetes manifest snippets (like deployment.yaml, service.yaml, configmap.yaml, and in Vector’s case, vectoragent.yaml for the CRD) that use Go’s templating syntax ({{ }}) to reference values from values.yaml.
Here’s how it breaks down for the Vector chart:
image.repositoryandimage.tag: These directly control theimagefield in theDeploymentorDaemonSetthat runs the Vector agent pods.# Inside the Helm chart's templates/deployment.yaml (simplified) spec: template: spec: containers: - name: vector image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"serviceAccount.createandserviceAccount.name: These determine if a KubernetesServiceAccountis created and what its name is. Ifcreateistrue, aServiceAccountresource is generated. This service account is then referenced in thespec.template.spec.serviceAccountNamefield of theDeployment/DaemonSet.resources: These translate directly into theresourcesblock within the container spec of theDeployment/DaemonSet. This is crucial for Kubernetes to schedule your pods effectively and prevent resource starvation or overuse.# Inside the Helm chart's templates/deployment.yaml (simplified) spec: template: spec: containers: - name: vector resources: requests: cpu: "{{ .Values.resources.requests.cpu }}" memory: "{{ .Values.resources.requests.memory }}" limits: cpu: "{{ .Values.resources.limits.cpu }}" memory: "{{ .Values.resources.limits.memory }}"config: This is where the magic for Vector’s own configuration happens. Theconfigkey invalues.yamlis often used to inject a multi-line string directly into aConfigMapor, as is common with Vector CRDs, into thespec.configfield of aVectorAgentcustom resource.
The# Inside the Helm chart's templates/vectoragent.yaml (simplified) apiVersion: apis.vector.dev/v1alpha1 kind: Vector metadata: name: {{ include "vector.fullname" . }} spec: config: |- {{ .Values.config | nindent 8 }}| nindent 8is a Jinja-like filter that indents the multi-line string content by 8 spaces, ensuring it’s correctly formatted within the YAML structure.
The values.yaml file is the primary interface for customizing a Helm chart’s behavior. You can override almost any default setting defined within the chart’s values.yaml (which is often provided by the chart maintainer) by simply providing your own values.yaml file or using --set flags on the command line.
One aspect often overlooked is how complex conditional logic and looping can be implemented within values.yaml and the Helm templates. For instance, you might have a tolerations list in your values.yaml. The Helm chart can then iterate over this list to generate multiple toleration objects for your pod spec.
# Example values.yaml snippet for tolerations
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
- key: "special-node"
operator: "Equal"
value: "true"
effect: "NoExecute"
This would be templated into something like:
# Inside the Helm chart's templates/deployment.yaml (simplified)
spec:
template:
spec:
tolerations:
{{- range .Values.tolerations }}
- key: {{ .key | quote }}
operator: {{ .operator | quote }}
{{- if .value }}
value: {{ .value | quote }}
{{- end }}
effect: {{ .effect | quote }}
{{- end }}
This allows for highly dynamic generation of Kubernetes manifests based on a structured data file, making Helm charts incredibly powerful and flexible.
The next step in understanding Helm is exploring how to create your own charts or how to manage more complex dependencies between charts.