Oct 12, 20265 min read

How to use templatefile in Terraform

Use templatefile to generate configuration files from Terraform variables.

Terraform's templatefile function is commonly used for cloud-init scripts, configuration files, and other resources that expect a string rather than structured HCL. In essence, it reads a file, replaces the variables in it, and returns the rendered text.

In this guide, we'll cover variables, loops, conditionals, and the cases where jsonencode or yamlencode is a better fit. We'll also explain why templates fail when they reference files created during apply, and why secrets usually should not be passed through user data.

We'll also look at when you can avoid this templating layer altogether, for example when it exists mainly to pass database or queue configuration into an application, and where Encore fits.

Render a template with variables

An EC2 instance that starts an orders service can keep its bootstrap script in cloud-init.tftpl. This example assumes Docker is already installed in the AMI:

#!/usr/bin/env bash set -euo pipefail image_ref=$(printf '%s' '${image_ref_base64}' | base64 --decode) docker run --detach \ --name '${service_name}' \ --publish '${port}:${port}' \ -- "$image_ref"

The Terraform configuration passes an object as the second argument to templatefile, and every key in that object becomes a variable inside the file:

locals { cloud_init = templatefile("${path.module}/cloud-init.tftpl", { service_name = "orders" port = 8080 image_ref_base64 = base64encode("${var.container_image}:${var.container_tag}") }) } resource "aws_instance" "orders" { ami = var.ami_id instance_type = "t3.micro" user_data = local.cloud_init }

The image reference is base64-encoded before rendering and decoded into a quoted shell variable. Shell metacharacters in an image value are therefore treated as data, and -- ends Docker's option parsing. The service name and port in this example are fixed values; deploy only images you trust.

Using path.module makes the path relative to the module containing the expression instead of whichever directory happens to run Terraform. The template must be valid UTF-8 and must exist when the run begins; templatefile is evaluated as part of configuration processing and cannot depend on a resource creating the file later.

Although Terraform does not enforce the .tftpl suffix, using it distinguishes an input template from the rendered file and gives editors enough information to apply the right syntax highlighting.

Use loops and conditionals without losing readability

Terraform templates support interpolation with ${...} and directives such as %{ for ... } and %{ if ... }. An Nginx upstream file can render a list of addresses and include an optional health-check endpoint:

upstream ${service_name} { %{ for address in addresses ~} server ${address}:${port}; %{ endfor ~} } %{ if health_path != null ~} location ${health_path} { proxy_pass http://${service_name}; } %{ endif ~}
locals { nginx_config = templatefile("${path.module}/upstream.tftpl", { service_name = "orders" addresses = ["10.0.1.10", "10.0.2.10"] port = 8080 health_path = "/healthz" }) }

The ~ marker strips adjacent whitespace, including newlines, on the side of the directive where it is placed, which avoids blank lines and indentation artifacts when a loop expands. Templates remain easiest to review when HCL prepares the data and the file is responsible only for presentation. Once a template starts calculating subnet ranges, merging defaults, or choosing between several object shapes, move that logic into locals and pass the finished value in.

Generate JSON and YAML safely

To keep quotes, commas, and escaped characters out of the template logic, use one interpolation and let jsonencode serialize the HCL value:

${jsonencode({ service = service_name port = port backends = [for address in addresses : { address = address port = port }] })}

For YAML, use the same pattern with yamlencode. Both functions handle quoting and escaping, and the HCL expression remains type-aware until the final string is produced. If the entire document is small, the encoding call can stay directly in the resource without a separate file:

locals { task_config = jsonencode({ service = "orders" port = 8080 }) }

Use file() instead when the content is literal and needs no interpolation. A provider's structured arguments are better still when they exist, since Terraform can then validate and diff individual fields rather than treating the whole document as an opaque string.

Avoid the common templatefile traps

Rendered content can be stored in Terraform state or exposed through a plan, provider API, instance metadata, or logs, depending on the resource that receives it. Marking a variable as sensitive only redacts some CLI output; it does not turn cloud-init or a generated configuration file into a secret store. Workloads should normally retrieve secrets at runtime through a scoped identity rather than having Terraform bake credentials into a template.

Templates also cannot call templatefile recursively, and a missing object key fails the render rather than becoming an empty string. When the inputs are known at plan time, Terraform reports the mismatch during planning. If an input is unknown until apply, an evaluation error can surface later.

When a template becomes large enough to behave like a program, it is usually time to move the behavior into an image, a configuration-management tool, or the application itself. Terraform is then responsible for selecting the artifact and passing a small amount of configuration, which gives plans a much clearer relationship to what will actually run.

When application infrastructure does not need a template

Encore

Templates remain useful for machine configuration and resources that accept text documents, but not when their only job is connecting an application to its own database, queue, bucket, or scheduled job.

Encore lets those application resources be declared alongside the code that uses them, while capacity, networking, and other environment-specific settings stay in environment configuration. The local runtime starts local versions of the declared resources, and a deployment provisions their cloud equivalents, so there is no generated application config file for Terraform to render and pass between the two systems.

Terraform can continue to manage the shared network, IAM, accounts, and any resource outside Encore's model. In practice, templatefile remains useful where a cloud resource expects rendered text, but not merely as a bridge between application code and application infrastructure.

Learn how Encore works →

Frequently asked questions

What does templatefile do in Terraform?

Using Terraform string-template syntax, templatefile fills the variables supplied in its second argument into a UTF-8 file on disk and returns the rendered text.

What is the syntax for templatefile?

The syntax is templatefile(path, vars), where path points to a file that already exists and vars is an object whose keys become variables inside the template.

Should Terraform template files use the .tpl or .tftpl extension?

Terraform accepts any filename, but HashiCorp recommends .tftpl because editors can recognize it as a Terraform template.

Can templatefile read a file created by another Terraform resource?

No. The template file must already exist on disk when Terraform starts the run. Keep it in source control rather than having a resource or provisioner create it during the same operation.

Should secrets be passed through templatefile?

Usually not. Rendered values can appear in plans, state, instance user data, logs, or provider APIs. Prefer a runtime secret manager and give the workload an identity that can retrieve only the secrets it needs.

Can I connect an application to its database without generating configuration files?

Yes. With Encore, you declare the database in application code and use its SDK to access it. Encore starts the database locally and supplies the connection configuration when deploying to your cloud account, so you do not need a template just to pass database settings into the application.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders