Renders a Helm chart's manifests locally for inspection, GitOps hand-off, or drift-comparison workflows, without creating a Helm release, wrapping
hashicorp/helm~> 3.2'shelm_templatedata source.
- ⎈ Renders a chart's manifests locally, mirroring the
helm templateCLI command, without touching the cluster's Helm release history. - 🔒 Performs no cluster mutation, and in most rendering modes, no cluster read either —
rendering proceeds from the chart's own metadata plus caller-supplied
api_versions/kube_version. - 🧬 Reuses
tf-mod-helm-release's values-injection variable shapes (values_yaml,set_values,set_list_values,set_sensitive_values) and secret-shaped-name validation for consistency across this library. - 🚫 Never accepts cluster-authentication material; the calling root module owns the
helm/kubernetesprovider block.
💡 Why it matters:
helm_templatelets a GitOps pipeline or a reviewer see exactly what a chart would render before anyhelm_releaseapplyexecutes chart hooks inside a live, regulated cluster — without granting that pipeline any cluster-mutating permission.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
AKS["Azure AKS module\noutputs (optional, validate=true only)"]:::neutral
KV["Key Vault / Managed Identity\nmodule outputs"]:::neutral
PROV["Caller-owned provider config\n(helm + kubernetes blocks)"]:::neutral
THIS["tf-mod-helm-template\n(data.helm_template.this)"]:::keystone
OUT["Rendered manifests\n(GitOps hand-off / drift comparison)"]:::aks
AKS -.->|"cluster read, only if validate = true"| PROV
KV -->|"secret values for set_sensitive_values"| THIS
PROV -->|"configures"| THIS
THIS -->|"renders"| OUT
classDef keystone fill:#0F1689,color:#fff
classDef aks fill:#277A9F,color:#fff
classDef neutral fill:#E5E7EB,color:#111
This provider has no sibling tf-mod-helm-* family tree above it. The AKS module's outputs feed
this diagram's provider-configuration edge only as a dotted, conditional line — most rendering
runs entirely offline, with cluster reachability required only when validate = true.
flowchart LR
subgraph INPUTS["Inputs"]
ID["name, namespace, chart,\nrepository, chart_version"]:::neutral
VAL["values_yaml, set_values,\nset_list_values, set_sensitive_values"]:::neutral
RC["api_versions, kube_version,\nshow_only, include_crds, validate,..."]:::neutral
end
THIS["data.helm_template.this"]:::keystone
subgraph OUTPUTS["Outputs"]
O1["manifest, manifests\n(sensitive)"]:::neutral
O2["notes, crds\n(sensitive)"]:::neutral
O3["id"]:::neutral
end
ID -->|"identity + chart source"| THIS
VAL -->|"values injection"| THIS
RC -->|"rendering context"| THIS
THIS -->|"emits"| O1
THIS -->|"emits"| O2
THIS -->|"emits"| O3
classDef keystone fill:#0F1689,color:#fff
classDef neutral fill:#E5E7EB,color:#111
Resource inventory: exactly one data source, data.helm_template.this — no resource is
created by this module.
| Component | Requirement |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/helm provider |
~> 3.2 (verified live against v3.2.0) |
| Provider block | None — the calling root module configures provider "helm" {} / provider "kubernetes" {}; required even for offline rendering, since the provider itself still needs a valid configuration block to load |
Schema notes that bite:
set,set_list, andset_sensitiveare list-of-nested-objects in provider v3.x, assigned with=syntax — identical shape totf-mod-helm-release, confirmed per the live provider schema against v3.2.0.versionis a reserved variable name inside Terraform module blocks; this module exposes it aschart_version.manifest,manifests,notes, andcrdsare technically "Optional" in the live schema (Plugin Framework Optional+Computed attributes) but are always computed by the render — this module models them exclusively as outputs, never as inputs.atomic,create_namespace,wait, andtimeoutdo not exist on this module's variable surface by design — they have no meaning for a non-mutating render (see 🧠 Architecture Notes).
helm_template performs no cluster mutation. In most rendering modes it performs no cluster
read either:
- No RBAC by default when
api_versions/kube_versionare supplied andvalidate = false(this module's default). - Read access to whatever the chart's templates inspect only if the chart calls cluster-lookup
template functions (
lookup,.Capabilities) and the caller has not suppliedapi_versions/kube_version— document per-chart; this module grants none by default. - Read access sufficient for OpenAPI schema validation if
validate = trueis set — still no mutation, but the identity must reach the Kubernetes API server.
- Kubernetes server version: 1.14.0 or higher (provider-wide floor, same as
tf-mod-helm-release). - Chart repository/registry reachability: same as
tf-mod-helm-release— outbound reachability torepositoryif set. - CRDs: rendering does not require pre-existing CRDs unless a chart's templates call
lookupagainst a CRD's custom resources.include_crds/skip_crdscontrol the chart's own CRD manifests in the output, independent of cluster state. - Namespace existence: not required — this module never creates or checks for a namespace.
tf-mod-helm-template/
├── providers.tf # Terraform + provider version pin, no provider block
├── variables.tf # Chart source, values injection, rendering-context arguments
├── main.tf # data.helm_template.this — the module's only data source
├── outputs.tf # manifest/manifests first (sensitive), then notes/crds/id
├── README.md
├── SCOPE.md
└── examples/
└── basic/
└── main.tf
provider "helm" {
kubernetes = {
config_path = "~/.kube/config"
}
}
module "mariadb_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "mariadb-instance"
namespace = "default"
chart = "mariadb"
repository = "https://charts.helm.sh/stable"
api_versions = ["v1", "apps/v1"]
}
output "mariadb_manifest" {
value = module.mariadb_preview.manifest
sensitive = true
}Consumes
| Input | Type | Source module |
|---|---|---|
AKS cluster API endpoint + credentials (only used if validate = true, or if omitted api_versions/kube_version fall back to live cluster inspection) |
provider configuration (caller-owned) | Azure AKS module or az aks get-credentials |
Key Vault secret values for set_sensitive_values |
variable input | Azure Key Vault module outputs |
| Managed Identity client ID (workload identity annotations) | variable input, via set_values/values_yaml |
Azure Managed Identity module outputs |
Emits
| Output | Description | Sensitive |
|---|---|---|
manifest |
Concatenated rendered chart templates | Yes |
manifests |
Map of rendered templates keyed by template path | Yes |
notes |
Rendered NOTES.txt content |
Yes |
crds |
Rendered CRD manifests from the chart | Yes |
id |
The data source's Terraform resource ID | No |
1 · Minimal HTTPS-repository render
module "redis_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "redis-preview"
namespace = "caching"
chart = "redis"
repository = "https://charts.bitnami.com/bitnami"
}ℹ️ With no
api_versions/kube_versionsupplied, charts that don't branch on.Capabilities/lookuprender identically offline or online.
2 · OCI registry chart source
provider "helm" {
kubernetes = {
config_path = "~/.kube/config"
}
registries = [
{
url = "oci://private.registry.casey.example"
username = "REPLACE_ME"
password = "REPLACE_ME"
}
]
}
module "internal_chart_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "internal-chart"
namespace = "platform"
repository = "oci://private.registry.casey.example/charts"
chart = "internal-chart"
chart_version = "1.4.2"
}3 · Local chart path
module "internal_service_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "internal-service"
namespace = "platform"
chart = "./charts/internal-service"
}4 · Faking Capabilities for fully offline rendering
module "offline_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "app-preview"
namespace = "platform"
chart = "./charts/app"
api_versions = ["v1", "apps/v1", "networking.k8s.io/v1"]
kube_version = "1.29.0"
}💡 Supplying
api_versions/kube_versionlets charts that branch on.Capabilitiesrender without any cluster read at all. See 🧠 Architecture Notes for the fidelity limits of this approach.
5 · Raw values_yaml heredoc
module "grafana_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "grafana"
namespace = "observability"
chart = "grafana"
repository = "https://grafana.github.io/helm-charts"
values_yaml = [
<<-YAML
persistence:
enabled: true
size: 10Gi
YAML
]
}6 · set_values for scalar overrides
module "mariadb_scaled_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "mariadb-instance"
namespace = "default"
chart = "mariadb"
repository = "https://charts.helm.sh/stable"
set_values = [
{ name = "service.port", value = "13306" },
]
}7 · set_sensitive_values for secret-shaped material
module "mariadb_with_secret_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "mariadb-instance"
namespace = "default"
chart = "mariadb"
repository = "https://charts.helm.sh/stable"
set_sensitive_values = [
{ name = "rootUser.password", value = "<from-key-vault>" },
]
}🔒 Even though this is a preview render, the secret-shaped-name rejection still applies to
set_values— the value ends up in the (sensitive) rendered manifest output either way, and the validation keeps callers in the habit of usingset_sensitive_values.
8 · show_only for targeted template rendering
module "mariadb_master_only" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "mariadb-instance"
namespace = "default"
chart = "mariadb"
repository = "https://charts.helm.sh/stable"
show_only = [
"templates/master-statefulset.yaml",
"templates/master-svc.yaml",
]
}9 · CRD inclusion for pre-installation review
module "cert_manager_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "cert-manager"
namespace = "cert-manager"
chart = "cert-manager"
repository = "https://charts.jetstack.io"
include_crds = true
}ℹ️
crdsoutput additionally exposes the CRD manifests separately from the mainmanifestoutput, for pipelines that pre-install CRDs as their own cluster-scoped step.
10 · Live-cluster OpenAPI validation
module "validated_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "internal-app"
namespace = "platform"
chart = "./charts/internal-app"
validate = true # Requires read access to the target cluster's API server — see SCOPE.md.
}11 · Chart signature verification
module "verified_chart_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "verified-chart"
namespace = "platform"
chart = "verified-chart"
repository = "https://charts.example.com"
verify = true
keyring = "/etc/casey/helm/pubring.gpg"
}12 · is_upgrade context for upgrade-branching templates
module "app_upgrade_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "internal-app"
namespace = "platform"
chart = "./charts/internal-app"
is_upgrade = true
}13 · Multi-environment for_each composition
locals {
environments = {
dev = { namespace = "app-dev", chart_version = "1.2.0" }
prod = { namespace = "app-prod", chart_version = "1.1.5" }
}
}
module "app_preview_per_env" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
for_each = local.environments
name = "internal-app"
namespace = each.value.namespace
chart = "internal-app"
repository = "https://charts.example.com"
chart_version = each.value.chart_version
}💡
for_eachover a keyed map, nevercount— consistent withtf-mod-helm-release.
14 · Drift-comparison against a live tf-mod-helm-release
module "app_release" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-release.git?ref=v1.0.0"
name = "internal-app"
namespace = "platform"
chart = "internal-app"
repository = "https://charts.example.com"
}
module "app_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "internal-app"
namespace = "platform"
chart = "internal-app"
repository = "https://charts.example.com"
}
# Compare module.app_release's applied state against module.app_preview.manifest in CI to detect
# drift between what Terraform would install and what is actually rendered from the same chart
# call, before promoting a change.15 · 🏗️ End-to-end composition — AKS module → provider → rendered manifest
module "aks" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-aks-cluster.git?ref=v1.0.0"
#... AKS cluster inputs...
}
provider "helm" {
kubernetes = {
host = module.aks.cluster_endpoint
cluster_ca_certificate = base64decode(module.aks.cluster_ca_certificate)
exec = {
api_version = "client.authentication.k8s.io/v1beta1"
command = "kubelogin"
args = ["get-token", "--login", "workloadidentity", "--server-id", module.aks.oidc_issuer_url]
}
}
}
module "platform_ingress_preview" {
source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
name = "nginx-ingress-controller"
namespace = "ingress"
chart = "nginx-ingress-controller"
repository = "https://charts.bitnami.com/bitnami"
validate = true # Uses the AKS provider config above to validate against the live cluster's OpenAPI schema.
}
output "ingress_manifest_preview" {
value = module.platform_ingress_preview.manifest
sensitive = true
}🏗️ This is the mandatory end-to-end shape: an Azure AKS module's outputs feed the caller's
provider "helm" {}block, enablingvalidate = trueto reach the live cluster's OpenAPI schema, while this module itself still creates no release and mutates nothing.
Grouped summary:
| Group | Variables |
|---|---|
| Identity | name, namespace, description |
| Chart source | chart, repository, repository_ca_file, repository_cert_file, repository_key_file, repository_username, repository_password, chart_version, devel, verify, keyring |
| Rendering context | api_versions, kube_version, show_only, skip_tests, include_crds, skip_crds, validate, is_upgrade |
| Values injection | values_yaml, set_values, set_list_values, set_sensitive_values |
| Other knobs | dependency_update, pass_credentials, reset_values, reuse_values, disable_webhooks, disable_openapi_validation |
Full object schemas
variable "set_values" {
type = list(object({
name = string
value = optional(string)
type = optional(string)
}))
default = []
}
variable "set_list_values" {
type = list(object({
name = string
value = list(string)
}))
default = []
}
variable "set_sensitive_values" {
type = list(object({
name = string
value = string
type = optional(string)
}))
default = []
sensitive = true
}See variables.tf for the complete set, including every scalar's description heredoc and
validation {} block.
| Output | Description | Sensitive |
|---|---|---|
manifest |
Concatenated rendered chart templates | Yes |
manifests |
Map of rendered templates keyed by template path | Yes |
notes |
Rendered NOTES.txt content |
Yes |
crds |
Rendered CRD manifests from the chart | Yes |
id |
The data source's Terraform resource ID | No |
- v2 → v3 syntax migration.
set,set_list, andset_sensitiveare list-of-nested-objects in provider v3.x, identical in shape totf-mod-helm-release. A v2-era example will failterraform validateagainst this module's~> 3.2pin. - Fidelity caveat.
helm_template's rendering can differ from whathelm_releasewill actually install if the chart's templates branch on live-cluster lookups (.Capabilities,lookupagainst cluster objects) that this data source cannot fully evaluate without the same cluster contexthelm_releasehas at apply time. Supplyingapi_versions/kube_versionnarrows but does not eliminate this gap. - No chart-hooks caveat needed. Unlike
tf-mod-helm-release, there is no apply step at all for a data source — chart hooks never execute forhelm_template, so this module has none oftf-mod-helm-release's "hooks execute outside Terraform's visibility" concern. atomic,create_namespace,wait,timeoutintentionally absent. These lifecycle/ mutation arguments have no meaning for a non-mutating render and are not modeled as variables.upgrade_installdoes not exist on this data source at all — it is ahelm_release-only concept; a caller expecting parity withtf-mod-helm-releasehere will not find it.
| Default | value | Upstream provider default | What it prevents |
|---|---|---|---|
namespace |
required, no default | defaults to "default" |
Incorrect .Release.Namespace template context and namespace fields in rendered manifests |
validate |
false |
false |
Requiring live cluster access for a tool positioned as an offline preview |
verify + keyring pairing |
keyring required when verify = true |
no pairing enforced | Unsigned/unverifiable charts silently rendering as if verified |
Secret-shaped set_values/set_list_values names |
rejected by validation {} |
not enforced | Secrets landing in plan output and CI logs in plaintext, and normalizing the habit of using set_sensitive_values |
set_sensitive_values |
entire variable sensitive = true |
set_sensitive masked at the argument level |
Secret values echoing in plan/apply output |
manifest/manifests/notes/crds outputs |
sensitive = true unconditionally |
not marked sensitive by the provider | Rendered manifests (which may contain injected secrets) printing in terraform output/CI logs |
dependency_update |
false |
false |
Uncontrolled network calls to chart-dependency repositories during rendering |
cd tf-mod-helm-template
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module source with an immutable tag, never a branch:
git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0
Even though this module performs no cluster mutation, terraform plan/apply are still run by a
human, from CI, after code review — never by this authoring process.
terraform init -backend=false / validate / fmt -check prove schema and syntax correctness,
including this module's validation {} blocks (secret-shaped name rejection, verify/keyring
pairing, type enum checks). They do not prove rendering fidelity against a live cluster's
actual .Capabilities — that requires either supplying real api_versions/kube_version values
sourced from the target cluster, or running with validate = true against a live connection.
$ terraform output
manifest = <sensitive>
manifests = <sensitive>
notes = <sensitive>
crds = <sensitive>
id = "mariadb-instance"
| Symptom | Cause | Fix |
|---|---|---|
Rendered manifest looks different from what tf-mod-helm-release actually installs |
The chart's templates branch on live-cluster .Capabilities/lookup calls this data source cannot fully evaluate |
Supply api_versions/kube_version matching the target cluster, or set validate = true against a live connection for higher fidelity |
terraform validate fails on a set { name =... value =... } block |
v2.x-era HCL block syntax, incompatible with this module's ~> 3.2 pin |
Rewrite as set_values = [{ name = "...", value = "..." }] |
terraform validate fails with "set_values contains a secret-shaped name" |
A values path like rootUser.password was passed through set_values |
Move the entry to set_sensitive_values |
manifest/manifests output is empty |
show_only was set to a template path that does not exist in the chart |
Confirm the exact template path via helm show chart <chart> or the chart's own template directory listing |
A caller expected upgrade_install/atomic/wait variables here |
Those are helm_release-only concepts |
Use tf-mod-helm-release for actual installs; this module only renders |
- hashicorp/helm provider docs
helm_templatedata source reference- This module's
SCOPE.md - Sibling module:
tf-mod-helm-release(for actual chart installs) - The Azure AKS module this module is typically composed with
💙 "Infrastructure as Code should be standardized, consistent, and secure."