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.repository and image.tag: These directly control the image field in the Deployment or DaemonSet that 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.create and serviceAccount.name: These determine if a Kubernetes ServiceAccount is created and what its name is. If create is true, a ServiceAccount resource is generated. This service account is then referenced in the spec.template.spec.serviceAccountName field of the Deployment/DaemonSet.
  • resources: These translate directly into the resources block within the container spec of the Deployment/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. The config key in values.yaml is often used to inject a multi-line string directly into a ConfigMap or, as is common with Vector CRDs, into the spec.config field of a VectorAgent custom resource.
    # 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 }}
    
    
    The | nindent 8 is 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.

Want structured learning?

Take the full Vector course →