Accessing Terraform variables within user_data provider template file
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When provisioning EC2 instances or other cloud VMs with Terraform, the user_data field runs a startup script on first boot. To make this script dynamic — passing in hostnames, database endpoints, API keys, or environment names — you inject Terraform variables into the script using template interpolation. Terraform supports two approaches: inline templatefile() function (recommended) and the legacy template_file data source. Both use ${variable} syntax inside the template, but they differ in how variables are passed and managed.
Using templatefile() (Recommended)
templatefile() is a built-in Terraform function that reads a file and substitutes ${variable} placeholders with the values passed in the second argument (a map). This is the recommended approach since Terraform 0.12+.
Using the Legacy template_file Data Source
The template_file data source was the standard approach before templatefile() was added. It requires the template provider and creates an extra resource in the state. Prefer templatefile() for new code.
Passing Lists and Maps
Template directives like %{ for ... } and %{ if ... } allow loops and conditionals inside the template. The ~ trims whitespace around the directive.
Conditional Logic in Templates
Inline user_data with Heredoc
Inline heredocs use standard Terraform variable interpolation (${var.name}) directly. This works for short scripts but becomes hard to maintain for complex bootstrapping.
Base64 Encoding for user_data
Common Pitfalls
- Using
${}in shell scripts without escaping: Terraform interprets${...}as variable interpolation. If your bash script uses ${BASH_VAR}, Terraform tries to substitute it and fails. Escape literal dollar signs as$${BASH_VAR}in the template so Terraform outputs ${BASH_VAR}in the rendered script. - Forgetting to pass variables to
templatefile(): Every${variable}used in the template must be included in the vars map. If a variable is referenced in the template but not passed, Terraform raises an error at plan time. Pass all required variables explicitly. - Changing user_data forces instance replacement: AWS treats
user_dataas a launch-time attribute. Changing it in Terraform forces destruction and recreation of the instance, not an in-place update. Uselifecycle { ignore_changes = [user_data] }if you want to prevent replacement, or accept that user_data changes require new instances. - Template file not found due to path issues: Use
${path.module}/scripts/init.shto reference files relative to the Terraform module. Relative paths withoutpath.modulebreak when the module is called from a different directory. - Exceeding user_data size limits: AWS limits user_data to 16 KB. Long scripts with many variables may exceed this. Move complex bootstrapping logic to a configuration management tool (Ansible, cloud-init) and use user_data only to trigger it.
Summary
- Use
templatefile("file.sh", { var = value })to inject Terraform variables into user_data scripts - Template files use
${variable}for interpolation and%{ for/if }for control flow - Escape shell
${}variables as $${}to prevent Terraform from interpreting them - Use
${path.module}for reliable file path resolution - Inline heredocs work for simple scripts; external template files are better for complex ones
- Changing user_data forces instance replacement — plan accordingly

