How to Fix Invalid Template String Interpolation with ${var} in Terraform 0.12
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins
TL;DR
- What broke: Terraform 0.12 introduced a native type system and first-class expressions. Legacy
"${var.foo}"wrapping in non-string contexts and${...}insidetemplatefile()heredocs now throws a parser error or produces unexpected type coercion. - How to fix it: Remove redundant
"${...}"wrappers where a direct reference suffices; replace inlinetemplate_filedata sources withtemplatefile()built-in function. - Use the Client-Side Sandbox above to auto-refactor your HCL — paste your failing config and get corrected output instantly.
The Incident (What Does the Error Mean?)
Raw error from terraform plan:
Error: Invalid template interpolation value
on main.tf line 12, in resource "aws_instance" "web":
12: ami = "${var.ami_id}"
The template interpolation on line 12 is not valid. A single template
directive with no surrounding text is redundant — use the expression
directly, e.g. var.ami_id instead of "${var.ami_id}".
Or the harder-to-debug variant when mixing types:
Error: Incorrect attribute value type
Expected a string, got a number wrapped in an interpolation.
Immediate consequence: terraform plan hard-fails. No diff is produced. CI pipeline blocks. If this is gating a deployment, your infra change is dead in the water until this is resolved.
The Attack Vector / Blast Radius
This is not just a cosmetic lint warning. The blast radius is operational:
Type coercion silently breaks logic. In 0.11, everything was a string.
"${var.instance_count}"coerced a number to string transparently. In 0.12, that same pattern forces a string type where a number is expected — causing downstreamcountorfor_eachexpressions to throw type mismatch errors that are non-obvious to trace.Module consumers break on upgrade. If a shared module uses legacy interpolation and a consuming team upgrades their Terraform CLI to 0.12+, every caller of that module fails simultaneously. One unfixed module = org-wide blast.
template_filedata source is deprecated and broken. Code still usingdata "template_file"with${var.x}inside thetemplateargument will silently produce wrong output or fail outright when the variable is a complex type (list, map).
How to Fix It (The Solution)
Basic Fix — Remove Redundant Interpolation Wrappers
When the entire string value is a single variable or expression reference, drop the quotes and the ${} entirely.
# main.tf
resource "aws_instance" "web" {
- ami = "${var.ami_id}"
- instance_type = "${var.instance_type}"
- count = "${var.instance_count}"
+ ami = var.ami_id
+ instance_type = var.instance_type
+ count = var.instance_count
}
Interpolation syntax is still valid and required when embedding a variable inside a larger string:
# Still correct — interpolation inside a mixed string
- tags = { Name = var.env }
+ tags = { Name = "${var.env}-web-server" }
Enterprise Best Practice — Migrate template_file to templatefile()
The data "template_file" pattern is the most common source of 0.12 interpolation failures in large codebases. Replace it entirely.
# BEFORE — legacy 0.11 pattern
-data "template_file" "user_data" {
- template = "${file("${path.module}/templates/userdata.sh.tpl")}"
- vars = {
- environment = "${var.environment}"
- db_host = "${var.db_host}"
- }
-}
-
-resource "aws_instance" "app" {
- user_data = "${data.template_file.user_data.rendered}"
-}
# AFTER — native 0.12+ pattern
+resource "aws_instance" "app" {
+ user_data = templatefile("${path.module}/templates/userdata.sh.tpl", {
+ environment = var.environment
+ db_host = var.db_host
+ })
+}
Inside the .tpl file itself, variable references use ${var_name} (no var. prefix — these are template-local variables, not Terraform variables):
# templates/userdata.sh.tpl
-#!/bin/bash
-echo "Env: ${var.environment}"
-echo "DB: ${var.db_host}"
+#!/bin/bash
+echo "Env: ${environment}"
+echo "DB: ${db_host}"
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
Don't let this reach code review. Enforce it at commit time.
1. terraform validate in every PR pipeline
# .github/workflows/terraform.yml
- name: Terraform Validate
run: |
terraform init -backend=false
terraform validate
This catches type mismatch interpolation errors before plan.
2. tflint with the AWS ruleset
tflint --enable-rule=terraform_deprecated_interpolation
The terraform_deprecated_interpolation rule flags every "${single_expr}" wrapper. Zero config required.
# .tflint.hcl
rule "terraform_deprecated_interpolation" {
enabled = true
}
3. Checkov for policy-as-code gates
checkov -d . --framework terraform
Checkov's static analysis parses the AST and will surface template interpolation anti-patterns alongside security misconfigs in one pass.
4. terraform 0.12upgrade CLI command (one-time migration)
If you're migrating an existing 0.11 codebase, run the official upgrade tool first — it auto-rewrites the most common interpolation patterns:
terraform 0.12upgrade
Review the diff it produces. It handles ~80% of cases automatically. The remaining 20% — especially template_file → templatefile() migrations — require manual intervention as shown above.