Learn Helm Chart - Configuring Charts with Values
Episode 6 of 30

Learn Helm Chart - Configuring Charts with Values

Values are the interface between users and charts: the precedence hierarchy from default values.yaml, -f files, to --set on the CLI, how to set values, values file structure for complex data, and per-environment configuration patterns like values-dev.yaml and values-prod.yaml.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Introduction

After episode 5, where we covered upgrade, rollback, and release history — how changes are safely applied to a running release — in this episode we step back half a pace to dissect the main fuel of every Helm operation: values.

Why does this topic matter? Imagine a chart as an application with many settings, and values as its control panel — all the buttons and dials that determine application behavior. In the real world, most of a DevOps/Platform engineer's daily Helm work isn't writing Go templates, but crafting the right values file for a given environment: how many replicas for production, which image tag to deploy to staging, how much CPU to allocate to service X. The most common production errors aren't broken templates, but wrong values — a wrong image tag, an overridden secret, or resource limits that get Pods evicted. Understanding the precedence hierarchy and how values merging works is what separates "a helm install that happens to work" from "helm installs you can manage across hundreds of releases."

In this episode we'll dissect the values precedence hierarchy, the various ways to set values from the CLI, the merge rules for multiple files, values file structure for complex data types, examples of common configuration, and most importantly: the per-environment values strategy used by production teams.

Main Discussion

The Values Precedence Hierarchy

Helm determines a parameter's final value from three layers, from lowest to highest priority:

PrioritySourceWhen It's Used
LowChart's built-in values.yamlDefaults defined by the chart author; always loaded
MediumUser values files (-f / --values)Configuration from you; can be more than one file
High--set, --set-string, --set-file flagsQuick overrides straight from CLI/CI

The rule of thumb: the more specific, the more it wins. --set beats files, files beat chart defaults. The analogy is adjusting the AC in a building: values.yaml is the default temperature of the whole building, values files are per-floor settings, and --set is the manual knob you turn right now for this room.

Precedence in a single command
helm upgrade --install frontend bitnami/nginx \
  --namespace web \
  --values values.yaml \
  --set image.tag=1.27.0

In the example above, image.tag from --set overrides the image.tag in values.yaml, which overrides the default in the chart's built-in values.yaml. Parameters not touched at all fall to the lowest available value.

Note

Merging is a deep merge for maps: values at the top level are preserved if not overridden at a lower level. But for lists/arrays, there's no merging — a list from a higher-priority source replaces the entire list from a lower-priority source. This is a frequent source of bugs: changing one item in an args list via --set will delete the other items defined in the values file. Always be aware of the data type you're overriding.

Setting Values from the CLI: --set, --set-string, --set-file

These three flags are how Helm accepts values directly from the command line — the main weapon in CI/CD pipelines.

--set follows dot-based path syntax and can combine several values at once with commas:

Multiple values in one --set
helm upgrade --install frontend bitnami/nginx \
  --set image.tag=1.27.0,replicaCount=3 \
  --set resources.requests.cpu=250m,resources.requests.memory=512Mi

Numeric-looking values (like 3 and 250m) and true/false will be interpreted as their data types, not strings. Lists can be written with curly braces: --set providers={gcp,aws,azure} produces a list of three items. Values containing commas or special characters need escaping, and for keys with a literal dot, use a backslash: --set nodeSelector."kubernetes\.io/os"=linux.

--set-string forces every value to be treated as a string, whatever its form. This matters for values that look numeric but must be strings — for example, a version code like 1.0 that would lose its trailing zero if parsed as a number, or a phone number.

Force values as strings
helm upgrade --install frontend bitnami/nginx \
  --set-string app.version=1.0 \
  --set-string http.cors.origins="https://a.com,https://b.com"

--set-file reads the contents of a file as a value — usually to embed file content (for example, an nginx.conf configuration or a public key) directly into a template rendered as a multiline string:

Read file contents as a value
helm upgrade --install frontend bitnami/nginx \
  --set-file nginx.conf=/etc/nginx/custom.conf

Tip

Be careful how --set handles values with special characters like $, [, ], *, and ". In a shell, those values can be interpolated or expanded before Helm sees them. Always wrap values in quotes (--set 'key=$VALUE'), and for truly unruly values, it's safer to put them in an -f file than to craft escapes on the CLI.

Multiple Values Files & Merge Order

Helm accepts multiple -f files, and files listed later win over files listed earlier. The chart defaults are always the bottom layer, below all files:

Merging multiple values files
helm upgrade --install frontend bitnami/nginx \
  --namespace web \
  --values values-common.yaml \
  --values values-web.yaml \
  --values secrets-placeholder.yaml

The reading flow: rendering starts from the chart's values.yaml → overwritten by values-common.yaml → overwritten again by values-web.yaml → overwritten by secrets-placeholder.yaml. Deep merge rules apply: if values-common.yaml sets replicaCount: 2 and values-web.yaml sets replicaCount: 5, the final result is 5 — but if values-web.yaml only sets image.tag, the replicaCount value from values-common.yaml is preserved.

The "base + override" pattern is the foundation of the environment strategy we'll discuss shortly. Imagine cooking with a base recipe and adjusting the seasoning per menu: the first file holds ingredients shared by every menu, subsequent files only hold the differences for a specific menu.

Values File Structure: Nested YAML, Lists, Complex Data

Values files are ordinary YAML, and because YAML supports nested structures, values can hold very expressive configuration:

A complete values structure
replicaCount: 3
 
image:
  repository: ghcr.io/company/frontend
  tag: "1.27.0"
  pullPolicy: IfNotPresent
 
resources:
  requests:
    cpu: 250m
    memory: 512Mi
  limits:
    cpu: "1"
    memory: 1Gi
 
service:
  type: ClusterIP
  port: 80
  annotations:
    prometheus.io/scrape: "true"
 
ingress:
  enabled: true
  hosts:
    - host: app.company.com
      paths:
        - path: /
          pathType: Prefix
 
env:
  LOG_LEVEL: info
  ALLOWED_ORIGINS:
    - https://web.company.com
    - https://api.company.com
 
labels:
  team: platform
  cost-center: core
An example structure common in production charts

Note the types that appear: nested maps (resources.requests.cpu), lists of maps (ingress.hosts with elements containing host and paths), lists of strings (env.ALLOWED_ORIGINS), and deliberate booleans/strings — the value tag: "1.27.0" is quoted so it isn't interpreted as a number. This type consistency matters because templates access these with .Values.<path> and type errors are one of the most common causes of helm template failing at render.

Important

The golden rule of values structure: values that depend on the environment must be data, not logic. Don't put "ifs" inside values; values must be purely declarative. If you feel the need for global:, make sure it's really needed — global values propagate to every subchart and can make configuration hard to trace. The global details will be covered when we discuss dependencies in episode 11.

Common Configuration in Production Charts

Even though every chart differs, there are five groups of configuration that are almost always set when deploying a production application:

  • Image & tag — registry, image name, version tag, and pullPolicy. Make sure tag is a string, and for production prefer pullPolicy: IfNotPresent (the image is already pulled locally) or Always for frequently changing commit hashes.
  • Resource limits/requests — this is the first thing production reviewers ask about: how much CPU and memory is guaranteed (requests) and the maximum (limits). Requests determine Pod scheduling, limits determine when the kernel/kubernetes throttles.
  • Replica count — the number of Pods; raise it for high availability, lower it in development to save resources.
  • Service typeClusterIP (internal), NodePort (accessible from outside via node ports), LoadBalancer (cloud LB), or ClusterIP + Ingress (the most common pattern).
  • Ingress host — the public domain + TLS; usually what differs most between environments.

All of these are just an interface — a good chart documents them in the README and its built-in values.yaml with comments. Your job as a chart user is to pick the right value per environment.

Per-Environment Values: values-dev, values-staging, values-prod

The chart is one, the environments are many. The same values for production (replica 6, memory 2Gi, host app.company.com) clearly don't fit development (replica 1, memory 256Mi, host app-dev.company.com). The industry standard pattern is to separate those differences into per-environment values files:

replicaCount: 1
image:
  tag: "dev"
  pullPolicy: Always
resources:
  requests:
    cpu: 100m
    memory: 256Mi
  limits:
    cpu: 250m
    memory: 512Mi
ingress:
  enabled: true
  hosts:
    - host: app-dev.company.com

Using them in CI/CD is simple: helm upgrade --install frontend ... --values values-prod.yaml. Note that the per-environment file only contains differences — values shared by every environment are best placed in the chart's built-in values.yaml or in a values-common.yaml loaded first.

Some organizational strategies used by production teams:

  • Per-environment values files in the application repo. One directory contains values-dev.yaml, values-staging.yaml, values-prod.yaml, plus values-common.yaml as the base. Simple, transparent, and reviewable in a single PR.
  • Thin overrides with --set in the pipeline. Values that are truly build-specific (the CI image tag, git commit SHA) are set directly, while static configuration goes in files. Example: --set image.tag=$CI_COMMIT_SHA.
  • Secrets never go into ordinary values files. Secret values (passwords, tokens, keys) are handled separately — via Kubernetes Secrets, Vault, External Secrets, or the mechanisms we'll cover in the security series. Committed values files only contain placeholders.
  • One chart, many releases. For truly different environments (e.g. different clusters), some teams create separate releases per namespace with different names (frontend-prod, frontend-staging) — the same chart, the same release pattern, different values.

Warning

The biggest danger of the per-environment pattern is environment drift: production and staging "never look the same," so bugs that pass staging show up in production. The only antidote is forcing staging to run a configuration as close to production as possible — starting with using the same values except those that genuinely must differ (host, resources, replica count), and deploying staging from the same image as production.

Verifying the Effective Values

After understanding how to set values, there's one discipline that's often skipped: verifying what actually applies. Because precedence can obscure where a value came from, never guess the final result — ask the release directly:

Show the effective values
helm get values frontend --namespace web
helm get values frontend --namespace web --all

The first command only shows values that differ from the chart defaults (those set via -f/--set); the --all option shows the full effective values after merging — this is the "final truth" that templates will render. To complete the audit, helm get manifest frontend --namespace web shows the rendered manifests that were actually sent to the cluster.

When does this matter? Two critical moments. First, after an upgrade — to make sure the override you thought succeeded wasn't overwritten by another file listed later. Second, when handing a release to another engineer: before touching someone else's release, read its helm get values first so you don't trample values deliberately set earlier. This is the same audit habit as reading a diff before merging — cheap, but it saves many incidents.

Tip

When arguing "which value applies," helm get values <release> --all is the final arbiter — not memory, not re-reading command history. For output you can grep or parse with other tools, add -o yaml or -o json: helm get values frontend -o json.

Conclusion

In episode 6 we've dissected how values are a configuration layer determined by a precedence hierarchy: the chart's built-in values.yaml as the base, -f files as medium overrides, and --set/--set-string/--set-file as the highest overrides. We learned the merge rules — deep merge for maps, total replacement for lists — how to set values safely from the CLI, values file structure for nested YAML and complex data, the five common configuration groups (image, resources, replica, service type, ingress host), and the values-dev.yaml, values-staging.yaml, and values-prod.yaml strategy with a base + override pattern.

Key takeaways:

  • The more specific the values source, the higher its priority: defaults < files < --set.
  • Lists are replaced entirely, not merged; maps are deep-merged.
  • Use --set-string for values that must be strings; --set-file to embed file contents.
  • Values files listed later win; use a base + per-environment pattern.
  • Secrets are never committed in values files; separate secret values from ordinary configuration.

Now you understand how to control charts from the outside. In the next episode, episode 7, we flip the perspective — from chart user to chart author: creating your first chart. We'll dissect helm create, the generated directory structure, the Chart.yaml file as metadata, designing good values.yaml, basic Deployment/Service/ConfigMap templates, and how to test a chart with helm lint, helm template, and helm install --dry-run. See you in episode 7!

Learn Helm Chart - Configuring Charts with Values | Learn Helm Chart