Fleet Templating
Templating helps you customize deployments for different clusters without maintaining separate configuration files. This helps you:
-
Deploy environment-specific image tags.
-
Use cluster labels to define regional configurations.
-
Generate dynamic values for Helm charts.
-
Reuse computed values across multiple manifests.
Fleet preprocesses the following files:
-
fleet.yaml -
Helm values files
-
Kubernetes manifests
-
HelmOps resources
When you install Helm charts using Fleet, it is important to distinguish between Fleet-managed files and internal Helm chart files:
-
Helm chart values.yaml (No templating):
-
The default values.yaml file bundled inside a Helm chart does not support Fleet templating. Any substitutions happen during the standard Helm chart rendering phase, before Fleet applies the chart. If you use ${var} in this file, Helm treats it as plain text.
-
-
Fleet valuesFiles and values (Templating supported):
-
Fleet allows you to reference additional values files through fleet.yaml using the valuesFiles or values keys. These Fleet-specific configurations fully support templating, allowing you to apply dynamic, environment-specific overrides.
-
How Fleet templating works
Before a Bundle is created, the Fleet controller scans specific files (like fleet.yaml and values.yaml). It searches for ${ } delimiters. These are used instead of standard Go-template {{ }} to avoid collisions with Helm’s own templating engine.
For example, if you have a values.yaml file with:
values:
region: ${ .ClusterLabels.region }
If the target cluster contains the label: region: us-west. Then Fleet generates:
values:
region: us-west
Fleet versus Helm templating
Fleet preprocessing happens before Helm rendering.
| Feature | Fleet Templating | Helm Templating |
|---|---|---|
Delimiter |
${ } |
{{ }} |
Timing |
Pre-processing (Manager side) |
Rendering (Cluster side) |
Context |
Cluster Labels, Annotations, Name |
Values.yaml, Chart metadata |
Processor |
Fleet Controller |
Helm Binary |
For example, if you have a fleet.yaml with:
values:
cluster: ${ .ClusterName }
config:
message: "{{ .Values.message }}"
In this example:
-
Fleet resolves ${ .ClusterName }
-
Helm later resolves {{ .Values.message }}
Value precedence
When Fleet prepares the final values for a deployment, it merges configuration from multiple sources. If the same key exists in multiple places, the source with the highest precedence wins. Here are possible sources of values, listed in decreasing order of precedence:
-
templateValues: Values generated here always overwrite other sources. Use this for logic-heavy variables. -
targetCustomizations: Overrides defined for specific cluster groups. -
helm.valuesFrom: Values loaded dynamically from ConfigMaps or Secrets on downstream clusters. These take precedence over files and inline values. -
helm.valuesFiles: External YAML files referenced in the helm block. These override inline helm.values. -
helm.values: Global inline values defined in your Fleet.yaml. -
Chart values.yaml: The default values bundled inside the Helm chart.
Accessing Cluster data
Fleet injects specific cluster information into the template context. You can access these using the following keys:
| Variable | Description | Example |
|---|---|---|
.ClusterName |
The metadata name of the Cluster resource. |
${ .ClusterName } |
.ClusterLabels |
Map of labels assigned to the Cluster. |
${ .ClusterLabels.env } |
.ClusterAnnotations |
Map of annotations assigned to the Cluster. |
${ .ClusterAnnotations.owner } |
.ClusterValues |
Custom values in the Cluster’s spec.templateValues. |
${ .ClusterValues.apiEndpoint } |
.ClusterNamespace |
The namespace where the Cluster resource resides. |
${ .ClusterNamespace } |
Expressions and logic
You can also leverage Sprig functions in Fleet to use complex string manipulation and logic.
|
Avoid using Sprig functions that produce random output (for example, uuidv4). Randomly generated values change on every evaluation and will trigger endless, continuous redeployments. Always guard against null values when writing your templates to prevent rendering errors. |
Default Values
Use default to ensure a deployment doesn’t fail if a label is missing.
values:
region: ${ default "us-east-1" .ClusterLabels.region }
Conditional Logic
Change configuration based on the environment.
values:
# Sets 'stable' for prod, 'latest' for others
imageTag: ${ if eq .ClusterLabels.env "prod" }stable${ else }latest${ end }
Use templateValues for cluster-specific data
You can define cluster-specific data mappings directly on the Cluster resource that represents a downstream cluster, using its templateValues field. The Cluster resource lives in the Fleet management cluster, in the workspace namespace (for example fleet-default). These values are then passed into the Fleet templating engine and can be consumed in your fleet.yaml (or HelmOp) files using the .ClusterValues variable.
Variable Reuse
You can define a value once in the templateValues field of the Cluster resource and reference it multiple times in the values block using the .ClusterValues prefix.
Define the value once on the Cluster resource:
apiVersion: fleet.cattle.io/v1alpha1
kind: Cluster
metadata:
name: prod-eu
namespace: fleet-default
spec:
templateValues:
region: eu-west
Reference it in fleet.yaml as often as needed:
helm:
values:
region: ${ index .ClusterValues "region" | default "us-east-1" }
ingress:
host: app.${ index .ClusterValues "region" | default "us-east-1" }.example.com
Using index with default keeps the deployment working on clusters that do not set region. A plain ${ .ClusterValues.region } fails to render on such clusters, and index without default renders <no value>.
Each Cluster can carry different values, so the same fleet.yaml renders differently on each cluster it targets.
Structural Generation (Advanced)
Standard YAML blocks must be valid YAML before processing. This prevents you from using range or if to generate lists or maps. Because templateValues is a map of strings, it bypasses this restriction. Fleet evaluates the string first and then parses the result as YAML.
Each key in helm.templateValues becomes a top-level key in the Helm values. If the same key also exists in values, the templateValues entry replaces it entirely. The two are not merged. Keys in helm.templateValues are not available as template variables, so you cannot reference them from values.
| helm.templateValues in fleet.yaml is not the same as the templateValues field of the Cluster resource, which is read with .ClusterValues. |
The following example creates one ingress host entry for each host name listed on the Cluster resource.
apiVersion: fleet.cattle.io/v1alpha1
kind: Cluster
metadata:
name: prod-eu
namespace: fleet-default
spec:
templateValues:
hosts:
- app.example.com
- app.example.org
helm:
templateValues:
ingressHosts: |-
${- range index .ClusterValues "hosts" }
- host: ${ . }
${- end }
Using index keeps the deployment working on clusters that do not set hosts. They get no host entries.
This results in the following Helm values for that cluster:
ingressHosts:
- host: app.example.com
- host: app.example.org
Disable preprocessing
If you use other templating languages in your configurations and want to avoid conflicts with Fleet’s templating engine, you can turn off Fleet’s templating entirely in fleet.yaml.
helm:
disablePreProcess: true
This helps you template syntax conflicts with Fleet preprocessing, if you want to preserve raw template expressions.
This flowchart below provides a clear path for file processing.
Escaping expressions
To escape a single expression while keeping the engine active, use a double dollar sign ($$).
For example, value: $${ .ClusterName }, results in value: ${ .ClusterName }.
Test template expressions locally
You can test your values templating and preview how your expressions render using the Fleet command-line interface (CLI). The fleet target command evaluates a local bundle file against a live cluster and outputs the exact resources the Fleet controller would create.
Prerequisites: Unlike the fleet apply command, which can run entirely locally without a cluster, fleet target requires access to a running Kubernetes cluster where Fleet is installed.
-
Create a local bundle: First, use the fleet apply command to convert your existing local directory into a bundle file. Run fleet apply -o bundle.yaml.
-
Evaluate the targets: Run the fleet target command and pass the generated bundle file as an argument. You can specify a namespace as an argument so the CLI knows where to look for your target Cluster resources. Run fleet target -f bundle.yaml
-
Inspect the output: The command evaluates your targeting rules and prints the BundleDeployment and Content resources that would be created. Inspect this output to verify that your template expressions evaluated correctly against the specific cluster labels, names, and values.
Best practices
-
Whitespace Control: Use ${- and -} to strip leading/trailing whitespace, especially in templateValues where YAML indentation is sensitive.
-
Centralize Logic: Use templateValues for complex calculations to keep the main values block clean.
-
Defensive Coding: Always provide a default value for cluster labels to avoid empty string injections.
Troubleshooting templating
Template value is empty
-
Verify that the cluster label or annotation actually exists on the Cluster resource in the management plane.
-
Check that the template expression is valid Go syntax.
-
Ensure preprocessing is enabled for the specific file.
Template expression is not evaluated
-
Verify that the file type supports preprocessing (e.g., fleet.yaml, values.yaml).
-
Ensure disablePreProcess is not enabled in your configuration.
-
Confirm the syntax strictly uses the ${ } delimiter.
Helm template conflicts
-
Remember that Fleet and Helm use different template syntaxes.
-
Use ${ } for Fleet pre-processing and {{ }} for Helm templating.
-
Avoid mixing both in the same expression.
YAML Parse Errors
-
If using templateValues for complex blocks, use whitespace control (${- and -}) to ensure the generated YAML remains structurally valid. Incorrect indentation is the primary cause of deployment failures in logic-heavy templates.