Terraform is most commonly used to provision infrastructure — VMs, storage accounts, networking, etc. But sometimes, your infrastructure code also needs to prepare local files or artifacts, such as configuration files or deployment packages.
In this post, I’ll show how to use Terraform not only to define infrastructure but also to generate local files, dynamically populate configuration files using variables, and zip them for deployment — for example, to an Azure App Service.
Contents
🧩 Use Case
Let’s say you have a JavaScript frontend or some static content you want to deploy to Azure. This content includes a config.js file that needs to include the public IP of a backend VM — something dynamically known only at deployment time.
We’ll use Terraform to:
- Populate a config.js file from a template.
- Create a zip archive (content.zip) of the content folder.
- (Optionally) upload this zip to Azure.
🔧 Terraform Setup
Here’s the Terraform code that accomplishes steps 1 and 2:
terraform {
required_version = ">= 1.0.0"
}
provider "null" {}
variable "vm_ip" {
default = "1.2.3.4"
description = "IP address of the Azure VM"
}
# Generate config.js from template
resource "local_file" "config_js" {
filename = "${path.module}/content/config.js"
content = templatefile("${path.module}/config.js.tpl", {
# examples to fill variables values within config files
vm_ip = var.vm_ip
})
}
# Zip the entire content folder
data "archive_file" "zip_webclient" {
type = "zip"
source_dir = "./content"
output_path = "./content.zip"
depends_on = [local_file.config_js]
}
📝 config.js.tpl Template
This is your simple JavaScript template file that uses the variable:
// config.js.tpl
const config = {
backendIp: "${vm_ip}"
};
When Terraform runs, it generates the following file in ./content/config.js:
// Generated
const config = {
backendIp: "1.2.3.4"
};
📦 Why Use null_resource?
Yes, null_resource is often criticized because it doesn’t represent a real infrastructure resource. But in this case, it’s a valid and pragmatic choice to:
- Run local shell commands (like zipping).
- Trigger actions based on file generation (depends_on).
- Inject variables dynamically via templates.
You could also use external scripts or a CI/CD pipeline, but having this in Terraform ensures reproducibility and automation for small or self-contained deployments.
☁️ Bonus: Upload the Zip to Azure App Service (Optional)
You can go one step further and upload the content.zip to an Azure App Service using Terraform:
resource "azurerm_service_plan" "app_service_plan" {
name = "WebApp-Plan"
location = azurerm_resource_group.this.location #create
resource_group_name = azurerm_resource_group.this.name #create
sku_name = "B1"
os_type = "Windows"
}
resource "azurerm_windows_web_app" "win_webapp" {
name = "WebApp"
location = azurerm_resource_group.this.location #create
resource_group_name = azurerm_resource_group.this.name #create
service_plan_id = azurerm_service_plan.app_service_plan.id
virtual_network_subnet_id = azurerm_subnet.appservicesubnet.id #create
zip_deploy_file = data.archive_file.zip_webclient.output_path
site_config {
vnet_route_all_enabled = true
websockets_enabled = true
application_stack {
current_stack = "dotnet"
dotnet_version = "v8.0"
}
}
depends_on = [azurerm_subnet.appservicesubnet]
}
✅ Conclusion
Terraform is not a build tool — but with small tricks like templatefile, local_file, and local-exec, you can turn it into a lightweight solution for generating config files and preparing deployment artifacts.
This pattern works especially well when deploying to services like Azure App Services, where you want to prepackage content based on dynamically generated inputs.
💬 What Do You Think?
Have you ever used Terraform this way? Would you mix build logic with infra-as-code, or keep them separate? Share your thoughts in the comments!