Right, let’s talk about helm template. This is the command you run when you’ve stared at a values.yaml file for so long that the YAML starts to look like a modern art painting, and you desperately need to see what the heck Helm is actually going to send to your cluster. It’s your debugger, your truth-teller, your escape hatch from the magic-and-spells abstraction that Helm provides.

Think of it this way: helm install is the polished, final performance. helm template is the dress rehearsal where you get to see the actors in sweatpants and yell “line!” when they forget the words. It renders your charts locally, combines them with the values you provide, and then spits out the raw, unadulterated Kubernetes YAML manifests to stdout. No Tiller, no cluster, no questions asked. It’s just you, your chart, and the cold, hard truth of the generated YAML.

Why You’ll Love This Command

You’ll use this for three main reasons: debugging, debugging, and oh yeah, debugging. More specifically:

  1. Seeing the Final Output: Is that {{ .Values.service.port }} actually pulling the right value? Did that if statement in your template actually work, or did it just evaluate to an empty string? helm template shows you, instantly.
  2. Validating Your Chart’s Logic: Before you even think about running helm install, you can run template to catch syntax errors, missing values, or wonky conditional logic. It’s a linter on steroids.
  3. GitOps and Imperative Workflows: Maybe you’re not even allowed to run Helm in your cluster. Some GitOps purists prefer to commit the fully-rendered manifests instead of the Helm charts. helm template is how you generate those manifests. It’s also perfect for piping the output to kubectl apply -f - if you’re into that sort of imperative life.

Here’s the basic incantation. You point it at a chart (either a local directory or a chart from a repo) and give it a release name.

# From a local chart directory
helm template my-release ./my-chart/

# From a packaged .tgz chart
helm template my-release ./my-chart-1.2.3.tgz

# From a repo (you'll usually need --version and often --devel)
helm template my-release prometheus-community/prometheus --version 15.0.0

Run that, and you’ll get a firehose of YAML. Be ready to pipe it to less or a file.

Actually Using Your Values Files

The command above is useless because it only uses the chart’s default values.yaml. The whole point is to use your values. That’s what the -f or --values flag is for.

# The classic move: override the defaults with your custom values
helm template my-release ./my-chart -f my-values.yaml -f my-environment-overrides.yaml

# Want to see what happens if you enable that one feature?
helm template my-release ./my-chart --set ingress.enabled=true

# Go nuts and combine them. Order matters: the last set value wins.
helm template my-release ./my-chart -f production-values.yaml --set replicaCount=5

This is where you catch those “oh, I forgot to set that one value” moments. Always, always test with the exact values you plan to use for installation.

The Naked Truth: –show-only and –output-dir

Sometimes you don’t want the firehose. You just want to check one specific template. That’s where --show-only comes in. It’s a lifesaver.

# Just check the Service definition, please.
helm template my-release ./my-chart -f values.yaml --show-only templates/service.yaml

Even better, if you’re doing serious development and want to render everything but into separate files for easier diffing, use --output-dir. This is a game-changer.

# Renders each manifest to a file in ./my-output/
helm template my-release ./my-chart -f values.yaml --output-dir ./my-output/

# Now you can use all your normal dev tools on the output
ls ./my-output/my-chart/templates/
# deployment.yaml
# service.yaml
# configmap.yaml
# ...etc

The Sharp Edges and Pitfalls

Here’s the part the manual doesn’t yell loudly enough about: helm template does NOT validate the output against the Kubernetes API. It’s a template renderer, not a validator. It will happily generate a Deployment with spec.replicas: "five" (a string instead of an integer) because that’s a valid YAML value. Your cluster will rightfully reject it. Always follow up with kubectl apply --dry-run=client -f - or kubeval if you’re using this for pipeline work.

# The full, robust local debug workflow
helm template my-release ./my-chart -f values.yaml | kubectl apply --dry-run=client -f -

Also, be wary of charts that use the lookup function. This function tries to query your live Kubernetes cluster for information. Since helm template doesn’t connect to a cluster, lookup will return an empty list, which might break your template. It’s a bad practice in chart design for exactly this reason, but you’ll still see it out in the wild.

So, lean on helm template heavily. It demystifies the Helm magic and gives you direct control. Use it to see the strings and pulleys behind the curtain before you risk deploying a puppet show to production.