Terraform Guide

Terraform, Kubernetes and Helm: the cluster and its charts on one diagram

By the engineer who builds Datadef, from client work on data platforms · Reviewed August 21, 2026

A repository that provisions a cluster and installs its platform charts in the same apply has three layers stacked in one place: the cloud resources under the cluster, the Kubernetes objects created through the kubernetes provider, and the Helm releases. Reading it file by file, the layers blur. Drawn properly, the split is the most useful thing on the canvas.

6 min readFor teams whose Terraform provisions the cluster and installs the charts

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

151/20003 credits left
Try:

No account needed · Editable canvas, not a picture

Three layers, one state, one diagram

The bottom layer is cloud: a managed cluster, node pools, networking, an identity or two. The middle layer is the kubernetes provider: namespaces, deployments, services, ingresses declared as Terraform resources. The top layer is Helm, and it is unusually simple to draw, because the provider ships a single resource, helm_release, which is the whole layer.

Keeping all three in one repository is a deliberate choice with a real payoff: one plan, one state, one approval path instead of a terraform apply followed by a separate helm upgrade. The cost is that the repository now mixes infrastructure and application concerns, and a reader needs the boundary drawn for them.

What each layer looks like once parsed

Managed clusters are recognized per provider and drawn with the right mark: aws_eks_cluster with the EKS icon and its version attribute, azurerm_kubernetes_cluster with the AKS icon carrying kubernetes_version and sku_tier, google_container_cluster labelled GKE cluster with the Kubernetes icon.

On the Kubernetes side, kubernetes_deployment is treated as a major resource and carries its replicas, while kubernetes_service, kubernetes_namespace, and ingress resources are minor, which means several of them collapse into one node with the count and the member names rather than filling the canvas with squares.

helm_release is major and carries chart, version, and repository on the node. That is precisely what a reader wants to know about a platform chart, and it is the one place where a diagram beats reading the code, because those three values are usually spread across a locals block, a variable, and a for_each.

drawn node                     description on the box
-----------------------------  --------------------------------------
EKS cluster (aws_eks_cluster)  version = 1.29
nginx-ingress (helm_release)   chart nginx-ingress-controller 4.10.1
external-dns (helm_release)    chart external-dns 1.14.5
app (kubernetes_deployment)    replicas = 3
Supporting resources x6        services, namespaces, config maps

Where the arrows come from, and where they do not

Arrows come from references inside resource arguments, resolved through module outputs to the resources they actually reach. A helm_release whose values reference module.eks.cluster_name, or a kubernetes_deployment placed in a namespace created by kubernetes_namespace, gets a real labelled edge.

One honest gap is worth planning around. When a helm_release is tied to its cluster only through provider configuration, the common pattern where the helm provider block reads module.eks.cluster_endpoint, that relationship lives in a provider block rather than in the resource, and it does not become an arrow. If you want the dependency visible, reference the cluster from the release itself or route it through a resource that does.

The analysis is also not a cluster scan. There is no kubeconfig, no cluster API call, and no chart rendering, so a release is drawn as the release rather than expanded into the workloads the chart contains. For the objects side of the story, the manifest pipeline is covered in living diagrams from Kubernetes manifests.

Which type lands where, and why some do not land at all

The classification is a table of known types plus keyword rules for everything else, applied in a fixed order, so the same repository produces the same picture on every sync. Managed clusters, kubernetes_deployment, and helm_release are the known majors. A node carries at most three attribute values, taken in a priority order set per type.

What falls out of the rules is worth planning around. The first keyword rule matches names containing role, permission, grant, member, iam, or acl, so kubernetes_cluster_role_binding is classified as wiring and counted rather than drawn, before any Kubernetes rule gets a chance to look at it. The second rule catches secret, so kubernetes_secret is counted too, which is the right answer for a diagram and worth knowing if you were expecting a box.

One sharp edge: the known-type table is keyed on the unsuffixed names. A repository written against kubernetes_deployment_v1 rather than kubernetes_deployment still gets the Kubernetes mark, because the provider fallback supplies it, but it is treated as supporting detail and its replica count is not printed on the node.

declared in .tf                  drawn as
-------------------------------  ------------------------------------
aws_eks_cluster                  major, EKS icon, version on the box
azurerm_kubernetes_cluster       major, AKS icon, kubernetes_version
                                 and sku_tier on the box
google_container_cluster         major, "GKE cluster", Kubernetes icon
helm_release                     major, Helm icon, chart + version +
                                 repository on the box
kubernetes_deployment            major, replicas on the box
kubernetes_service / _namespace  minor, rolled up with a count
kubernetes_config_map            minor, Kubernetes mark
kubernetes_secret                wiring: counted in a note, no box
kubernetes_cluster_role_binding  wiring: counted in a note, no box

FAQ

Does this read my cluster or my kubeconfig?

No. The analysis parses the .tf and .tfvars files of the connected repository and nothing else. There is no cluster API call, no kubeconfig, no terraform init, no state file, and no cloud credentials involved, so the diagram describes what the code declares rather than what is currently running in the cluster.

Are Helm charts expanded into the objects they install?

No. Charts are not rendered, so a helm_release is drawn as one node carrying its chart name, version, and repository, the three values usually spread across a locals block, a variable, and a for_each. Expanding a chart would mean pulling and templating it, which is a different operation from parsing a repository and would need the chart repository to be reachable.

Which Kubernetes resources get their own box?

Managed clusters and kubernetes_deployment are treated as major and stay individual nodes, with the cluster version and the deployment replica count on them. Services, namespaces, and ingress resources are minor, so repeated ones roll up into a single node with the count and member names.

Why is there no arrow between my helm_release and the cluster?

Because the link is probably declared in the helm provider block rather than in the release. Arrows are resolved from references inside resource arguments. Referencing the cluster from the release itself, or from something the release depends on, makes the relationship appear.

Can the infrastructure layer and the application layer stay on one canvas?

Yes, and it is the point of keeping them in one repository. Modules render as zones, so the cluster module and the platform charts module sit side by side with the wiring between them drawn from real references. Each zone holds at most eight nodes before its remaining single resources fold into one counted Supporting resources node, which keeps the split readable.