Using Variables in Terraform

Andy Scott
5 min readOct 26, 2022

--

Terraform variables

Input variables let you customise aspects of Terraform modules without altering the module’s own source code. This functionality allows you to share modules across different Terraform configurations, making your module composable and reusable.

In this article we will explore how to use variables in terraform, recap on basic usage and include real world examples. Every example includes a link to the example GitHub repository.

Variables are important in making Terraform modules reusable and also enabling solutions to be deployed into multiple environments, regions and accounts.

Declaring variables

To declare a variable we need a variable block, we need to give the variable a name. It is also recommended to give the variable a description and a type.

variable "example" {
type = string
description = "example string to show how to declare a variable"
}

Below is a more complex example showing a default value, wether the variable is sensitive, condition and error message and wether the variable can be a null value.

variable "example" {
type = string
description = "example string to show how to declare a variable"
default = "example"
sensitive = true
condition = var.example == "example"
error_message = "This is an example, var == example only"
nullable = false
}

Assigning variables

Variables can be assigned a number of different ways allowing flexibility in solutions.

Environment variables

export TF_VAR_example="example"

Command line arguments

terraform apply -var="example=example"

TF Vars Files

terraform apply -var-file="example.tfvars"

In addition to these methods terraform will automatically take any values assigned from a file named terraform.tfvars or *.auto.tfvars. Finally if you haven’t assigned the variable correctly terraform will prompt you to manually enter the value when you run terraform commands that require these variables.

Hashicorp documentation is well written and easy to navigate, The link below gives further details of how to declare and assign variables in Terraform. Let’s go beyond the basic usage and explore some more complex scenarios.

Now we’ll look at some examples of how to use variables in real world situations. Im going to use this terraform module as our example, this module creates AWS VPC interface and gateway endpoints.

Basic
This example of module usage doesn't include any variables, the underlying module has lots of variables, some are required and some have defaults set. We have hardcoded all the values directly in main.tf, doing this would cause us issues if we intend to deploy the solution into multiple VPCs, multiple regions or multiple accounts.

# examples/basic/main.tfmodule "vpc_endpoints" {
source = "andyscott1547/vpc-endpoints/aws"
version = "1.0.3"
vpc_id = "vpc-1234567890"
interface_endpoints = [
"ec2",
"rds",
"sqs",
"sns",
"ssm",
"logs",
"ssmmessages",
"ec2messages",
"autoscaling",
"ecs",
"athena"
]
gateway_endpoints = [
"s3",
"dynamodb"
]
subnet_ids = [
"subnet-1234567890",
"subnet-0987654321",
"subnet-1234567890"
]
route_table_ids = [
"rtb-1234567890",
"rtb-0987654321",
"rtb-1234567890"
]
}

Multiple tfvars files
This example of module usage we have created an <environment>.tfvars file per environment. This solves the problems we saw previously when deploying to multiple VPCs, Regions and Accounts. Wether using CICD or not the terraform apply command will include -var-file argument.

# examples/tfvars_environment_specific/main.tfmodule "vpc_endpoints" {
source = "andyscott1547/vpc-endpoints/aws"
version = "1.0.3"
managed_private_dns_enabled = var.managed_private_dns_enabled
vpc_id = var.vpc_id
interface_endpoints = var.interface_endpoints
gateway_endpoints = var.gateway_endpoints
subnet_ids = var.subnet_ids
route_table_ids = var.route_table_ids
}

Combining variables and data lookups
This example of module usage we have decided that we don’t want to use a variable for the vpc_id, subnet_ids and route_table_ids but to deploy the same endpoints in each environment. Here we have used data lookups to retrieve the information we need to assign these variables within the module. To effectively use data lookups it is useful to have naming and tagging strategy, this allows us to filter results for the information we want. As the endpoints aren’t changing between environment i have used a *.auto.tfvars file so i don’t have to pass this as an argument to my terraform commands.

# examples/data_lookup/main.tfmodule "vpc_endpoints" {
source = "andyscott1547/vpc-endpoints/aws"
version = "1.0.3"
managed_private_dns_enabled = var.managed_private_dns_enabled
vpc_id = data.aws_vpc.current.id
interface_endpoints = var.interface_endpoints
gateway_endpoints = var.gateway_endpoints
subnet_ids = data.aws_subnets.current.ids
route_table_ids = data.aws_route_tables.current.ids
}

Auto tfvars with locals and data lookups
This example of module usage we are combining data lookups with some additional logic to access relevant variables from a map in *.auto.tfvars. This time we want to deploy different VPC endpoints into different environments to help optimise costs. We have used data lookups to retrieve the information we need to assign the vpc_id, subnet_ids and route_table_ids variables.

In *.auto.tfvars we have two sections, one for global variables and one for environment specific variables. The env specific section contains a map of maps for each environment. This is indexed in locals from a separate variable, this variable can be passed by environment variable or command line argument. This is particularly useful in automation, there is a more preferred way using Terraform workspaces but we’ll cover that next as not all backends support workspaces.

module "vpc_endpoints" {
source = "andyscott1547/vpc-endpoints/aws"
version = "1.0.3"
managed_private_dns_enabled = var.managed_private_dns_enabled
vpc_id = data.aws_vpc.current.id
interface_endpoints = local.environment.interface_endpoints
gateway_endpoints = local.environment.gateway_endpoints
subnet_ids = data.aws_subnets.current.ids
route_table_ids = data.aws_route_tables.current.ids
}

Terraform Workspace with auto tfvars

Terraform workspaces

Each Terraform configuration has an associated backend that defines how Terraform executes operations and where Terraform stores persistent data, like state.

The persistent data stored in the backend belongs to a workspace. The backend initially has only one workspace containing one Terraform state associated with that configuration. Some backends support multiple named workspaces, allowing multiple states to be associated with a single configuration. The configuration still has only one backend, but you can deploy multiple distinct instances of that configuration without configuring a new backend or changing authentication credentials.

Now we’ve recapped what a terraform workspace is…

This example of module usage we are using terraform workspaces and a *.auto.tfvars file that contains two sections, one for global variables and one for environment specific variables. The env specific section contains a map of maps for each environment. This is indexed in by the terraform workspace name, in this example we would have terraform workspace per environment so we can utilise the same code. When using terraform workspaces a new built in variable becomes available to us. terraform.workspace

Create a new terraform workspace

terraform workspace new <environment>
# examples/workspace_specific/main.tfmodule "vpc_endpoints" {
source = "andyscott1547/vpc-endpoints/aws"
version = "1.0.3"
managed_private_dns_enabled = var.managed_private_dns_enabled
vpc_id = local.environment.vpc_id
interface_endpoints = local.environment.interface_endpoints
gateway_endpoints = local.environment.gateway_endpoints
subnet_ids = local.environment.subnet_ids
route_table_ids = local.environment.route_table_ids
}

I hope you found this article useful, please clap 👏 and share.

--

--

Andy Scott

I’ve been working with AWS for 5 years since leaving the UK military. My career history includes Capital One, Amazon and most recently BJSS.