Imagine you are using your favorite IaC tool – Terraform, to deploy infrastructure for three environments: development, staging, and production. Each environment needs its own configurations, resource names, tags, and other customizations, but the underlying architecture remains the same. Without Terraform variables, you would create copies of the same configurations with minor changes in values. Terraform variables allow you to parameterize your code to be flexible, maintainable, and reusable. Mastering the use of variables is essential for creating configurations that are both reusable and flexible to changing requirements.
What Are Terraform Variables?
Terraform variables act as placeholders that allow you to parameterize your infrastructure configuration without modifying the underlying code. Similar to variables in programming languages, you declare them with names and assign values that Terraform interpolates during execution. For example, you can define a variable to specify an AWS region, instance type, or an S3 bucket name, which you can then reference throughout the Terraform scripts consistently.
Similar to most programming languages, Terraform variables have various types.
- Strings: Text values
- Numbers: Numeric values
- Booleans: true/false values
- Lists: Ordered collections of values
- Maps: Collections of key-value pairs
- Objects: Complex structured data
- Sets: Unordered collections of unique values
Here is how you define a variable in Terraform:
1 2 3 4 5 |
variable "instance_type" { description = "The type of EC2 instance to deploy" type = string default = "t2.micro" } |
You can then reference the variable across your code:
1 2 3 4 |
resource "aws_instance" "web_server" { ami = "ami-0c55b159cbfafe1f0" instance_type = var.instance_type } |
Using Variables in AWS Deployments
Managing Terraform variables effectively is essential in cloud environments such as AWS, where infrastructure spans multiple services and environments. Variables allow you to maintain consistency and standardize your complex AWS environments. For example, almost all AWS resources support tagging. You can enforce mandatory tags for all resources and use variables to configure the tag values.
Identifying variables
A good starting point is identifying the environment-specific (Infrastructure-related) data and naming conventions suitable for your deployments. Let’s take a scenario where you deploy an application for different customers on AWS. Each customer requires dedicated infrastructure. Some customers require several environments and have requirements on which region they need their application deployed.
We can recognize the following variables change based on the customer and environment:
- Customer Name
- Region
- Environment Name
Environment name and Customer Name are not AWS-related concepts. So we can categorize them for naming conventions.
The region is environment-specific and AWS related. Apart from the region, what else can we parameterize? Well, that depends on what AWS resources you deploy. For simplicity, let’s consider that this application runs on an EC2 instance and requires access to an S3 bucket. So, at a minimum, we should create a VPC, an EC2, an S3 bucket, and an IAM Policy.
We will use customer name as a prefix and environment as a suffix for resource names. This helps us create distinct and easily identifiable resources and ensures resource names are not repeated across deployments. Also note that resources such as S3 buckets and IAM Policies are global and cannot have the same name across regions.
1 2 3 4 5 6 7 8 9 10 |
variable "customer_name" { description = "Customer Name" type = string } variable "environment" { description = "Environment Name" type = string default = "dev" } |
We would need to create different VPCs without CIDR overlap, and we will also need to change the instance type for each customer. So, we will create some AWS-related variables that are also customer—and environment-specific.

Note that you can maintain all your variables in a separate file, typically named variables.tf
Identifying Locals
Terraform locals are a type of variable that you declare for internal calculations and derived values. You cannot provide them externally. If you want to construct a new variable based on some values, you should use locals.
Let’s use locals to satisfy the following requirements;
- We use tags in many of our resources. Locals can be used to create a single local variable that holds all our tags
- We will prefix all our resource names with “-”.
- We want to create two EC2 instances if the environment is prod
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
locals { common_tags = { Customer = var.customer_name Environment = var.environment ManagedBy = "Terraform" Project = "${var.customer_name}-${var.environment}" } name_prefix = "${var.customer_name}-${var.environment}" is_production = var.environment == "prod" ? true : false instance_count = local.is_production ? 2 : 1 } |
Creating reusable scripts
Let’s use these variables and locals in our script.
Step 1: Define your variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
variable "customer_name" { description = "Customer Name" type = string } variable "environment" { description = "Environment Name" type = string default = "dev" } variable "aws_region" { description = "AWS region" type = string default = "us-east-1" } variable "vpc_cidr" { description = "CIDR block for the VPC (e.g., 10.0.0.0/16)" type = string } variable "instance_type" { description = "EC2 instance type" type = string default = "t2.micro" } variable "ami_id" { description = "AMI ID for the EC2 instance" type = string } |
Step 2: Set local values and tags
1 2 3 4 5 6 7 8 9 10 11 12 |
locals { common_tags = { Customer = var.customer_name Environment = var.environment ManagedBy = "Terraform" Project = "${var.customer_name}-${var.environment}" } name_prefix = "${var.customer_name}-${var.environment}" is_production = var.environment == "prod" ? true : false instance_count = local.is_production ? 2 : 1 } |
Step 3: Configure the provider and networking resources
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
provider "aws" { region = var.aws_region } resource "aws_vpc" "main" { cidr_block = var.vpc_cidr tags = merge(local.common_tags, { Name = "${local.name_prefix}-vpc" }) } resource "aws_subnet" "main" { vpc_id = aws_vpc.main.id cidr_block = var.vpc_cidr tags = merge(local.common_tags, { Name = "${local.name_prefix}-subnet" }) } |
Step 4: Create a security group
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
resource "aws_security_group" "allow_ssh" { name = "${local.name_prefix}-sg" description = "Allow SSH inbound traffic" vpc_id = aws_vpc.main.id ingress { description = "SSH from anywhere" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = merge(local.common_tags, { Name = "${local.name_prefix}-sg" }) } |
Step 5: Launch EC2 instance(s)
1 2 3 4 5 6 7 8 9 10 11 |
resource "aws_instance" "web" { count = local.instance_count ami = var.ami_id instance_type = var.instance_type subnet_id = aws_subnet.main.id vpc_security_group_ids = [aws_security_group.allow_ssh.id] tags = merge(local.common_tags, { Name = "${local.name_prefix}-instance-${count.index + 1}" }) } |
✅ Step 6: Create an S3 bucket and IAM policy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
resource "aws_s3_bucket" "data" { bucket = "${local.name_prefix}-data" tags = merge(local.common_tags, { Name = "${local.name_prefix}-data" }) } resource "aws_iam_policy" "s3_access" { name = "${local.name_prefix}-s3-access" description = "Policy to allow access to the customer's S3 bucket" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = [ "s3:GetObject", "s3:ListBucket" ] Effect = "Allow" Resource = [ aws_s3_bucket.data.arn, "${aws_s3_bucket.data.arn}/*" ] } ] }) } |
Notice how we have used the common_tags and name_prefix locals across all our resources and variables we have declared as values for arguments in our resources. We also used the count meta-argument to conditionally set the number of EC2 instances based on our environment.
Declaring Outputs
Outputs are another kind of variable. You can use outputs to print the actual infrastructure information provisioned by your configurations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
output "vpc_id" { value = aws_vpc.main.id description = "The ID of the VPC" } output "instance_id" { value = aws_instance.web.id description = "The ID of the EC2 instance" } output "s3_bucket_name" { value = aws_s3_bucket.data.bucket description = "The name of the S3 bucket" } |
Declaring Outputs Provisioning with Variables
We can apply this script by substituting values;
Our script is now reusable and flexible, but there is still a problem. Depending on our deployment, we want to vary the values we use for each variable. We should also version-control which values we use for each deployment.
Terraform has a neat way to handle this requirement — learn how to automate your deployments. You can create a .tfvars file that sets these variables and overrides defaults. You can maintain this file in your version control.
Here is our .tfvars file. Let’s call it customerA-dev.tfvars.
1 2 3 4 5 |
customer_name = "controlmonkey" aws_region = "us-east-1" environment = "dev" vpc_cidr = "10.2.0.0/16" ami_id = "ami-084568db4383264d4" |
Let’s apply it with the command;
terraform apply -var-file=” ./tfvars/customerA-dev.tfvars ”
Let’s say we have another customer – CustomerB, and we should deploy their “prod” environment. Our “ customerB-prod.tfvars ” file would be
1 2 3 4 5 6 |
customer_name = "customerB" aws_region = "ap-northeast-1" environment = "prod" vpc_cidr = "10.5.0.0/16" ami_id = "ami-084568db4383264d4" instance_type = "t2.medium" |
Notice we did not provide instance_type for customerA. That is because we have set a default value for that variable.
4 Best Practices for Using Variables
1) Group related variables:
Organize variables by function or resource type.
2) Always include descriptions
Document what each variable is for:
1 2 3 4 |
variable "vpc_cidr" { description = "CIDR block for the VPC (e.g., 10.0.0.0/16)" type = string } |
3) Set sensible defaults:
Provide reasonable default values when appropriate:
1 2 3 4 5 |
variable "environment" { description = "Deployment environment" type = string default = "dev" } |
4) Validate inputs:
Use validation rules to prevent errors:
1 2 3 4 5 6 7 8 9 |
variable "instance_type" { type = string default = "t2.micro" validation { condition = contains(["t2.micro", "t3.small", "t3.medium"], var.instance_type) error_message = "Instance type must be t2.micro, t3.small, or t3.medium." } } |
5)Use variable files for environment-specific values:
Create separate .tfvars files for dev, staging, and production.
Common Terraform Variables Pitfalls to Avoid
1️⃣ Overusing variables:
Not everything needs to be a variable.
2️⃣ Use local values for intermediate calculations:
instead of using variables within resources:
1 2 3 |
locals { name_prefix = "${var.environment}-${var.project}" } |
3️⃣Neglecting sensitive data:
Mark sensitive variables accordingly.
1 2 3 4 5 |
variable "database_password" { description = "RDS database password" type = string sensitive = true } |
Terraform Variables Conclusion
The power of Terraform variables is in transforming your static infrastructure code into dynamic and reusable configurations. By using variables, you can maintain consistent deployments across multiple environments — and detect configuration drift early. Following best practices and being aware of common mistakes can help you develop secure IaC configurations that can grow alongside your organizational needs.
With ControlMonkey, you can automate Terraform variable management, enforce best practices, and streamline environment-specific deployments with AI-driven efficiency and multi-cloud compliance — request a demo or learn more in our resource library.

FAQ on Terraform Variables
Once you have declared the variables of your terraform configuration, you can define environment variables with the prefix `TF_VAR` to set values for those variables.
Prefix with TF_VAR_ for automatic mapping:
export TF_VAR_bucket_name=my-terraform-bucket
Terraform CLI also supports environment variables to modify its default behaviour.
You can set environment variables for Terraform in Windows using the Command Prompt or PowerShell. The syntax varies between the command-line interpreter you use:
Command Prompt:
set TF_VAR_region=us-west-2
PowerShell:
$env:TF_VAR_region = “us-west-2”
You can access output variables using the `terraform output` command after applying your configuration. Output variables will always be printed after a terraform apply command even if there are no changes to your infrastructure.
Terraform variables are declared in .tf files using the` variable` block.
variable "region" { description = "AWS region" type = string default = "us-west-2" }
Once you have declared the variables, you can pass values (or override) using;
- terraform.tfvars files
- Command-line flags: -var=”region=us-east-1″
- Environment variables (see below)