Terraform Template: Concepts, Use Cases and Examples

Terraform is an open-source infrastructure as code (IAC) software tool used to manage cloud infrastructure resources. It enables developers to define and provision infrastructure for their applications using a declarative language.

Terraform uses configuration files to describe the desired state of the infrastructure and automatically creates, updates, and deletes resources to match the desired state. One feature that makes these configuration files so efficient is Terraform templates.

In this blog post, we will cover key concepts behind Terraform templates, common use cases, and examples to get you up and running quickly.

To Learn Terraform’s key concepts from live demos and hands-on labs, ENROLL in our highly-rated Terraform Training Course

Try the Terraform Resource Attributes Lab for free

Terraform Resource Attributes Lab
Terraform Resource Attributes Lab

Understanding Terraform Templates

Terraform templates dynamically generate configuration files based on the state of your infrastructure or other external variables. Templates are perfect for dynamically creating cloud-init scripts, cloud-config files, or any other configuration files requiring variable data to be filled at runtime.

Terraform templates enable the embedding of variables, expressions, and loops within HCL strings, allowing the generation of complex infrastructure configurations. By using template interpolation syntax "${ ... }", users can embed variables and expressions, which are evaluated during the template rendering process.

Templates are typically stored in separate files with a .tpl or .tf.tpl extension, and they can be read and rendered using the template_file data source. This dynamic capability makes it possible to create multiple similar resources with varying attributes, adapt configurations to different environments, and automate repetitive tasks.

How to Use Terraform Template

Below are the steps for using a template in Terraform:

  • Create the Template File with a .tpl or .tf.tpl extension. This file will contain the Terraform template code using the HCL interpolation syntax ${ ... } to embed variables and expressions.
  • In your main Terraform configuration file, define the variables that will be used in the template.
  • Use the template_file Data Source to render the template with the provided variables.
  • Use the Rendered Template for verification or debugging purposes.

Let’s execute the steps mentioned above. Here is an example of using templates in Terraform to create an AWS S3 bucket with a dynamically generated bucket name.

Create a template file named s3_bucket.tpl as shown below:

resource "aws_s3_bucket" "my_bucket" {
  bucket = "${var.bucket_name}"
  acl    = "private"
}

In your main.tf file, define the bucket_name variable:

variable "bucket_name" {
  type	  = string
  default = "my-unique-bucket-name"
}

In the same main.tf file, use the template_file data source to render the template and pass the variable value:

data "template_file" "s3_bucket_template" {
  template = file("${path.module}/s3_bucket.tpl")
 
  vars = {
	bucket_name = var.bucket_name
  }
}

Now that the template is rendered, you can use the output to create the S3 bucket resource in the main.tf file:

resource "aws_s3_bucket" "my_bucket" {
  bucket = data.template_file.s3_bucket_template.rendered.bucket
  acl    = "private"
}

With this configuration, when you run `terraform apply`, Terraform will create an S3 bucket with the bucket name specified by the bucket_name variable.

Terraform Template Key Concepts

Below are Terraform template key concepts:

Template Interpolation:

Terraform allows you to embed variables or expressions inside a string or template to create dynamic content. In Terraform, Template Interpolation is denoted using the ${ ... } syntax. You can reference variables, call functions, attributes of resources, etc., by using the interpolation syntax.

Template Rendering Engine: Terraform has a template rendering engine that processes template files and replaces variables and expressions with their corresponding values before parsing these templates during the configuration apply phase and producing final configuration files.

User Data: User data templates are widely utilized when configuring AWS EC2 instances, enabling you to pass scripts or configuration data directly into new instances when they launch. Terraform supports user data templates as a method for dynamic script creation suited specifically for every instance being provisioned.

Data Sources: In addition to resource configurations, Terraform templates can also use data sources, which fetch information from the cloud provider's APIs or other external systems. These data sources can be used within templates to access specific details or metadata about cloud resources that might be needed for configuration generation.

Multi-File Templates: Terraform allows multiple template files, making it easier to organize and manage complex configurations. These files can be interpolated to form modular and reusable templates.

Conditional Logic and Loops: Terraform templates support conditional logic and loops, enabling more flexible and dynamic configurations. Conditional logic allows you to control the inclusion or exclusion of certain resources based on their values. Loops create multiple copies of resources with similar configurations.

Template Functions: Terraform provides various built-in functions that you can utilize in templates to manipulate data, format strings, perform mathematical operations and provide greater flexibility and power of template generation.

Output Values: Templates can include output blocks that define values to be displayed after applying the configuration, providing useful information about resources created during provisioning processes.

Terraform Template Use Cases

Terraform templates are incredibly versatile and can be applied to a wide range of situations. Let's take a look at some of the most common use cases.

Dynamic User Data: One of the most common use cases is in the creation of dynamic user data for cloud instances, especially in AWS EC2 instances. User data templates allow you to pass scripts, cloud-init configurations, or any other data to instances when they launch. By using template interpolation, you can inject instance-specific data into the user data, tailoring the scripts to each individual instance's requirements.

Custom Tags and Labels: Cloud resources often benefit from having custom tags or labels to identify their purpose or environment. Templates can dynamically set these tags based on variables or other contextual information. This ensures consistency and proper categorization of resources within your cloud infrastructure.

Configuration File Generation: Many applications and services require specific configuration files with values that differ between environments (e.g., development, staging, production). Templates can generate these configuration files using variables to adapt to each environment, reducing the need for manual adjustments and ensuring consistency across different environments.

Multiple Resource Instances: In certain scenarios, you may need to create multiple instances of the same resource with similar configurations. For instance, creating multiple identical Virtual Machines or Load Balancers. Templates combined with loops allow you to generate these resources in a concise and repeatable manner.

Dynamic Networking: In complex networking setups, you can use templates to define dynamic CIDR blocks, security group rules, or route tables based on input variables. This approach allows you to scale your networking configurations efficiently while maintaining proper segmentation and security.

Variable File Generation: For large deployments with many variables, you can use templates to generate variable files dynamically. This way, you can avoid duplication, keep variables organized, and ensure that all variables are correctly set for each environment.

Service Discovery: In environments where services or applications need to discover each other dynamically, templates can be employed to generate service discovery configuration files or DNS entries with the appropriate IP addresses or endpoints.

Certificate Generation: For TLS/SSL certificates, templates can assist in generating certificate signing requests (CSRs) and other certificate-related files. This automation streamlines the process of managing SSL certificates across various environments.

Infrastructure Reusability: Templates can be used to create reusable modules and components that can be shared across different projects. This promotes code reusability and reduces redundancy, as you can pass variables to modules to customize their behavior for specific use cases.

Dynamic Secrets Management: In environments where secrets are stored in a secret manager (e.g., HashiCorp Vault), templates can assist in generating configuration files with the proper secret values injected into them during provisioning.

Terraform Template Examples

Dynamic User Data for AWS EC2 Instances

Consider the code snippet below:

resource "aws_instance" "web_server" {
  ami       	= "ami-0n66b658cbfafe1f0"
  instance_type = "t2.micro"
  subnet_id 	= "subnet-0c53acdef12345678"
  tags = {
	Name    	= "Web Server"
	Environment = "Production"
  }
  user_data = <<-EOT
	#!/bin/bash
	echo "Hello, World! This script is running on ${self.private_ip}."
	echo "This instance is in ${aws_instance.web_server.subnet_id}."
  EOT
}

In this example, the user_data attribute is a template. It contains a bash script that will be executed when the EC2 instance launches. The ${self.private_ip} and ${aws_instance.web_server.subnet_id} expressions are interpolated within the script. During the provisioning process, Terraform will replace these expressions with the actual private IP address of the EC2 instance and the subnet ID in which the instance is located.

Dynamic Tags for Azure Virtual Machines

The code below creates an Azure Virtual Machine and uses a template to define its tags.

resource "azurerm_virtual_machine" "vm" {
  name                 	 = "demo-vm"
  location          	 = "East US"
  resource_group_name    = "my-resource-group"
  network_interface_ids  = [azurerm_network_interface.nic.id]
 
  tags = {
	Environment = "Development"
	Owner       = "Avi"
	Created_On  = "${formatdate("YYYY-MM-DD", timestamp())}"
  }
}

In this example, the "Created_On" tag uses the `formatdate` function to insert the current date in the "YYYY-MM-DD" format into the tag. This ensures that each time the configuration is applied, the VM will have a tag reflecting the creation date.

ENROLL in our AWS Cloud Practitioner to learn how to use AWS S3 and other AWS storage options from comprehensive Demos.

Dynamic Configuration Files for Docker Compose

The code below uses a template to create custom Docker compose files.

variable "app_name" {
  type	  = string
  default = "my-app"
}

variable "db_host" {
  type	  = string
  default = "db.example.com"
}
 
data "template_file" "docker_compose" {
  template = file("${path.module}/docker-compose.tpl")
 
  vars = {
	app_name = var.app_name
	db_host  = var.db_host
  }
}
 
resource "local_file" "docker_compose_file" {
  filename = "docker-compose.yml"
  content  = data.template_file.docker_compose.rendered
}

In this example, we use a Docker Compose template file named docker-compose.tpl that leverages dynamic variables for app_name and db_host to render our template file using template_file data source and render output directly into the local docker-compose.yml file. This approach makes creating customized Docker Compose files for specific applications or environments easy, allowing us to manage containerized applications across platforms and environments consistently.

ENROLL in our highly-rated FREE Docker Training course to learn more about how to use Docker Compose.

AWS S3 Bucket with Access Control

In this example, the aws_s3_bucket resource block creates an S3 bucket named "my-unique-bucket-name" in the specified region. The acl attribute is set to "public-read," which grants public read access to objects within the bucket.

provider "aws" {
  region = "us-west-2" 
}
 
resource "aws_s3_bucket" "demo_bucket" {
  bucket = "${var.bucket_name}" 
 
  acl  	= "public-read"
 
  tags = {
	Name    	= "Demo S3 Bucket"
	Environment = "Production"
  }
}

The aws_s3_bucket_policy resource block creates a bucket policy for the S3 bucket created in the previous resource block. The policy grants the public read access to objects in the bucket. The policy specifies a wildcard "*" as the principal, allowing public access to read objects within the bucket. The "arn:aws:s3:::${aws_s3_bucket.my_bucket.bucket}/*" specifies the ARN (Amazon Resource Name) for all objects within the bucket.

# Bucket policy to grant public read access
resource "aws_s3_bucket_policy" "my_bucket_policy" {
  bucket = aws_s3_bucket.my_bucket.bucket
 
  policy = <<EOF
{
  "Version": "2023-07-23",
  "Statement": [
	{
  	"Effect": "Allow",
  	"Principal": "*",
  	"Action": "s3:GetObject",
  	"Resource": "arn:aws:s3:::${aws_s3_bucket.my_bucket.bucket}/*"
	}
  ]
}
EOF
}

Use the template_file data source to render the template.

data "template_file" "s3_bucket_config" {
  template = file("${path.module}/s3_bucket.tpl")

  vars = {
    bucket_name = var.bucket_name
  }
}

ENROLL in our Terraform for Beginners Course to polish your Terraform skills in a real-world environment. It includes video lectures, interactive exercises, and hands-on labs to help you internalize concepts and commands.

Conclusion

Templates provide a powerful mechanism to dynamically generate configuration files, allowing for flexible, reusable, and efficient infrastructure provisioning and management. By leveraging templates, you can automate the provisioning of cloud resources efficiently while maintaining consistency and standardization across your infrastructure setup.

To gain other Infrastructure as Code (IaC) skills, check out our IaC Learning Path.

More on Terraform: