TL;DR
data "template_file"is deprecated and removed from modern Terraform.- Replace with the built-in
templatefile()function — same idea, no provider dependency. - Common pattern: replace
data.template_file.x.renderedwithtemplatefile("path.tpl", { vars }). - On Apple Silicon Macs the
templateprovider doesn’t compile —templatefile()is the only path.
What is migrating Terraform from template_file to templatefile()?
data "template_file" was Terraform’s original way to render a template with variable substitution. It lived in the hashicorp/template provider, which has been deprecated since Terraform 0.12 and is no longer maintained — it doesn’t even compile on Apple Silicon.
The replacement is templatefile(), a built-in function in the Terraform language itself. It takes a path and a map of variables, returns the rendered string. No provider, no data source, no plugin. The migration is mostly mechanical, but worth doing carefully because templates often render user-data scripts where a syntax error breaks the EC2 boot.
Prerequisites
- Terraform 0.12 or later (you should be on 1.5+ in 2026).
- Existing code using
data "template_file"(or the codebase wouldn’t need migrating). - A test environment to
planbefore applying — template rendering is hard to revert.
How to use this guide
The sections below walk through the practical commands and options. After the main content you’ll find a Verification block (sanity-check it actually worked), a Troubleshooting block (common error messages and what to do), and Related reading for follow-on topics.
Overview
I stumbled across this problem when using a new MacBook Pro M1 CPU. I had some legacy code that used aws_template for a user data script. The template function is now deprecated, and the only way around this is to use the templatefile a function that was introduced in Terraform 0.12.
This is simple for the new code, but if, like me, you have legacy code that is still embedded into production, you will have issues.
This is the error I was getting:
template v2.2.0 does not have a package available for your current platform, darwin_arm64There are many suggested fixes online, but they just fix the issue locally. It doesn’t fix the issue in the code and the terraform state files (mine are located in S3)
Migrating
Locate current usage
To begin, you can check to see what requires this provider in your current code – in the below example, you can see that the template provider is required by both resources in module.jenkins and the state:
Please note that I use AWS-Vault to access my cloud resources.
aws-vault exec <my-aws-profile> --no-session -- terraform providersProviders required by configuration: .
├── provider[registry.terraform.io/hashicorp/aws]
├── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10
├── provider[terraform.io/builtin/terraform]
└── module.jenkins
├── provider[registry.terraform.io/hashicorp/random]
├── provider[registry.terraform.io/hashicorp/template]
├── provider[registry.terraform.io/hashicorp/aws]
├── module.codebuild_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.iam_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.s3_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.security_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.slaves_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.network_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.ec2_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.lb_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.log_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.api_integration_autoscaling
│ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10
├── module.jenkins_alert_poli
│ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10
├── module.certs_label
│ └── provider[registry.terraform.io/hashicorp/aws]
└── module.api_integration_cloudwatch #
└── provider[registry.terraform.io/opsgenie/opsgenie]
0.6.10 Providers required by state: provider[registry.terraform.io/opsgenie/opsgenie] provider[registry.terraform.io/hashicorp/aws] provider[registry.terraform.io/hashicorp/random] provider[registry.terraform.io/hashicorp/template] provider[terraform.io/builtin/terraform]Removing Current usage
Search for all instances of the template_file data resource and replace it with a template file function – for example, this CloudWatch dashboard changes
From This:
data "template_file" "dashboard" {
template = file("${path.module}/templates/dashboard.template")
resource "aws_cloudwatch_dashboard" "jenkins" {
dashboard_name = "Jenkins" dashboard_body = data.template_file.dashboard.rendered
}
}To This:
resource "aws_cloudwatch_dashboard" "jenkins" {
dashboard_name = "Jenkins"
dashboard_body = templatefile("${path.module}/templates/dashboard.template", {})
}Full details of how to use the function, and pass variables, can be found here
Now, if you check the providers again, you should see that the modules are no longer listing template as a required provider – however, the state does:
aws-vault exec <my-aws-profile> --no-session -- terraform providersProviders required by configuration: .
├── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10
├── provider[registry.terraform.io/hashicorp/aws]
├── provider[terraform.io/builtin/terraform]
└── module.jenkins
├── provider[registry.terraform.io/hashicorp/aws]
├── provider[registry.terraform.io/hashicorp/random]
├── module.network_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.certs_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.codebuild_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.ec2_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.iam_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.api_integration_autoscaling
│ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10
├── module.api_integration_cloudwatch
│ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10
├── module.slaves_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.jenkins_alert_policy
│ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10
├── module.s3_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.security_label
│ └── provider[registry.terraform.io/hashicorp/aws]
├── module.log_label #
│ └── provider[registry.terraform.io/hashicorp/aws]
└── module.lb_label
└── provider[registry.terraform.io/hashicorp/aws]
Providers required by state: provider[registry.terraform.io/opsgenie/opsgenie]
provider[registry.terraform.io/hashicorp/aws] provider[registry.terraform.io/hashicorp/random] provider[registry.terraform.io/hashicorp/template] provider[terraform.io/builtin/terraform]Update State
Apply the Terraform and (assuming you got your code correct!) you should see that there were no changes to apply – this is because the template should be the same, just the method of generating it has changed:
aws-vault exec <my-aws-profile> --no-session -- terraform apply ... No changes.
Your infrastructure matches the configuration. Your configuration already matches the changes detected above. If you'd like to update the Terraform state to match, create and apply a refresh-only plan: terraform apply -refresh-only Apply complete! Resources: 0 added, 0 changed, 0 destroyed.After you successfully apply, you should refresh the state:
aws-vault exec <my-aws-profile> --no-session -- terraform apply -refresh-only .... No changes. Your infrastructure still matches the configuration. Terraform has checked that the real remote objects still match the result of your most recent changes, and found no differences. Would you like to update the Terraform state to reflect these detected changes? Terraform will write these changes to the state without modifying any real infrastructure. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes Apply complete! Resources: 0 added, 0 changed, 0 destroyed.Now when you check, you will see that the provider is no longer needed by the state either:
Providers required by state: provider[terraform.io/builtin/terraform] provider[registry.terraform.io/opsgenie/opsgenie] provider[registry.terraform.io/hashicorp/aws] provider[registry.terraform.io/hashicorp/random]At this point, you can remove the .terraform directory and re-init the code (terraform init -reconfigure) – all the modules and providers (now excluding the template provider) will be installed and the .terraform.lock.hcl will have its entry for the template provider removed.
At this point, you can commit your code back to git.
Verification
Sanity-check the change actually worked:
terraform plan— confirms notemplate_filedata sources remain.- Diff the rendered output:
terraform console, thentemplatefile("path.tpl", {...})— output should match the old.rendered. - Apply in a non-prod environment and confirm the consuming resource (EC2 user-data, etc.) boots correctly.
Troubleshooting
Variable interpolation looks right but renders empty — templatefile() is strict about types; pass strings as strings, numbers as numbers. tostring(var.x) is your friend.
Template uses heredoc syntax that templatefile() rejects — The syntax for ${...} is identical, but %{...} directives (if/for) work differently. The function docs have a worked example.
Provider still listed in required_providers — Remove the template entry from required_providers and run terraform init -upgrade.
Authoritative sources
References: Terraform — templatefile() function, hashicorp/terraform-provider-template (archived).


Leave a Reply