Escaping the Reusability Trap in Terraform and Helm

Escaping the Reusability Trap in Terraform and Helm

#gitops #helm #iac #terraform
Rachel Naane
March 23, 2026

One of the most common — and most confusing — questions when writing Terraform modules or Helm charts is what to hardcode and what to parameterize.

Terraform and Helm both encourage flexibility. They give us variables, value files, defaults, and templating mechanisms that make it easy to support many use cases with a single module or chart. On paper, this sounds ideal. In reality, it often leads to infrastructure that is hard to reason about, easy to misuse, and surprisingly fragile.

This article is about the tension between flexibility and structure, and about a methodology that helps me design Terraform modules and Helm charts that continue to serve teams well over time.

Hardcoding vs Parameterization: The Two Failure Modes in Terraform and Helm

Most teams hit two extremes at different stages.

The first is over-parameterization. This happens a lot in Helm, where it’s very tempting to turn every field in a manifest into a value. A Deployment becomes a thin template layer, with almost no opinion encoded in it. At that point, the chart no longer represents how the application is meant to run. It merely describes what Kubernetes allows.

Here’s a simplified example:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.name }}
spec:
  replicas: {{ .Values.replicas }}
  strategy:
    type: {{ .Values.strategy.type }}
  template:
    spec:
      containers:
        - name: {{ .Values.containers.name }}
          image: {{ .Values.containers.image }}
          imagePullPolicy: {{ .Values.containers.imagePullPolicy }}
          ports:
            - containerPort: {{ .Values.containers.port }}

The problem is not flexibility itself, but the lack of guardrails. Nothing prevents a user from deploying a configuration that technically works, but violates architectural, operational, or security assumptions. The chart becomes reusable, but not safe.

The second extreme is over-hardcoding, often combined with quietly relying on defaults. This typically happens in Terraform modules. You want a simple, clean module, so you hardcode what seems stable and let the provider defaults handle the rest. At the time, it feels reasonable — fewer variables, less noise, and faster progress.

The problem shows up later. A small, environment-specific need appears: you need to increase a load balancer idle timeout, or enable VPC flow logs to debug an issue. Both are simple changes, but the module can’t express them because they were never exposed. Instead of adjusting configuration, you’re forced to change the module itself and release a new version. The issue isn’t that you hardcoded or relied on defaults — it’s that flexibility was never a conscious decision.

The Question That Changes Everything

The real issue is not Terraform or Helm. It’s the question we start with.

Most infrastructure code begins by asking:
What might I want to reuse later?

That question naturally pushes us toward generic, flexible designs. Instead, I’ve found it much more useful to start with a different one:

What defines this product, and what is allowed to vary between environments?

Once you adopt this framing, many decisions become obvious.

Infrastructure as Product Code

Terraform modules and Helm charts should be treated like application code. They represent a product: an opinionated way of running a system.

Just like application code, they evolve over time. They gain features, change behavior, and sometimes introduce breaking changes. And just like application code, those changes should be explicit and versioned.

Changing how a system fundamentally works should not be hidden behind a new value of a variable. It should result in a new module or chart version.

Terraform Module Example: What to Hardcode vs What to Parameterize

Consider a Terraform module that provisions an RDS database.

The database engine is a fundamental architectural decision. Switching from PostgreSQL to MySQL is not a configuration tweak; it’s a breaking change that affects applications, tooling, and operational practices. That decision should be hardcoded in the module, and changing it should require publishing a new version.

On the other hand, things like instance size, storage type, or whether Performance Insights is enabled often differ between environments. Development and production rarely have the same requirements here. These are environment-specific concerns, and they should be parameterized.

By making this distinction explicit, the module becomes both safer and easier to reason about. It encodes architectural intent while still allowing controlled flexibility.

A Helm Example: Containers and Volumes

The same principle applies to Helm charts.

If your application currently runs as a single container per pod, there is no reason to support a list of containers in values.yaml. Doing so doesn’t make the chart “future-proof”; it just adds noise and opens the door to unsupported configurations.

The same goes for volumes. If the application does not use volumes today, the chart should not expose volume configuration options. Adding them preemptively increases complexity without providing real value.

If, in the future, the product genuinely requires an additional container or persistent storage, that is a meaningful change in how the application runs. It should be introduced deliberately, along with a new chart version that reflects this new reality.

A Simple Rule for Terraform and Helm Design Decisions

The rule I use is simple.

If a change would make you say, “This is no longer the same product”, it should be hardcoded and introduced via a new version.

If a change reflects a difference between environments, it should be parameterized.

This shifts the goal from maximizing reuse to preserving intent.

Why This Scales Better: Clarity, Safety, and Easier Reviews

This way of thinking creates clear boundaries. Modules and charts become easier to review, easier to operate, and harder to misuse. Versioning stops being something to avoid and becomes a natural part of how infrastructure evolves.

Most importantly, infrastructure starts telling a story. It becomes clear what is fixed by design and what is expected to vary.

The goal of Terraform modules and Helm charts is not unlimited flexibility. It is clarity, safety, and intentional change.

Stop asking what might be reused one day.
Start asking what defines your product today.

Once you make that shift, the balance between hardcoding and parameterization becomes much easier to find — and much easier to maintain over time.