Interested in learning more about env0?

Sam Gabrail

President at TeKanAid

How to Use Terraform Variables: Use Cases and Examples

Terraform is a powerful infrastructure as code (IaC) tool that enables you to define and manage your cloud infrastructure in a declarative manner. One of the key features of Terraform is the ability to use variables, which allow you to parameterize your configurations and make them more flexible and reusable.

Did you know that you could also order Domino's Pizza with Terraform? In this blog post, we will have fun with the Domino's Pizza Terraform provider while exploring the world of Terraform variables. We'll understand how to effectively use them in your infrastructure deployments.

Video Walk-through

Our setup

Let’s take a look at our setup.

Requirements

  • A GitHub account (all the hands-on sections will utilize GitHub’s Codespaces so you won’t need to install anything on your machine)

Repository

TL;DR: You can find the repo here.

What are Terraform Variables?

Terraform variables are used to customize modules and resources without altering their source code. They allow you to define and manage reusable values in your Terraform configurations, making it easier to adapt to changes and maintain consistency across different environments.

Importance of Terraform Variables

Using Terraform variables is essential for managing large infrastructure configurations, as it helps avoid hardcoding values and makes it easier to update and maintain your configurations. Variables also enable you to reuse code and create modular, scalable, and maintainable infrastructure.

Variable Types and Default Values

Terraform supports several variable types such as:

  • string
  • number
  • bool
  • list
  • map
  • object

You can also set default values for variables, making them optional. If no value is provided for a variable, Terraform will use the default value if it's available, otherwise will prompt the user to enter a value.

Defining Variables and Assigning Values

To define variables in Terraform, you use the variable block within your configuration files. This block specifies the name, type, default value, and other attributes of the variable. By defining variables, you inform Terraform about the inputs it expects during deployment. While variable blocks can be defined in any terraform configuration file, the convention is to define values in a separate variables.tf file in your Terraform project.

Syntax of Variable Blocks

The syntax for a variable block includes the variable keyword, followed by the variable name, and then a set of attributes enclosed in curly braces. The attributes can include the variable type, default value, description, and validation rules.

variable "variable_name" {
  type        = "variable_type"
  default     = "default_value"
  description = "variable_description"
  validation {
    condition = var.variable_name > 0
    error_message = "Variable must be greater than zero."
  }
}

Variable Description

You can add a description to your variable to provide more context and information about its purpose. The description attribute is optional and can be added within the variable block. For example:

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

Assigning Variable Values with a terraform.tfvars file

A terraform.tfvars file is used to assign variable values that are automatically loaded by Terraform during the apply or plan commands. This file should be created in the root directory of your Terraform project and should contain key-value pairs for your variables as shown below:

variable_name = "variable_value"

For example:

first_name   = "John"
last_name    = "Smith"

Furthermore, the convention is to add this terraform.tfvars file in your .gitignore file so that you don't check it into git. The reason for this is that you would typically define sensitive values in this file. You can also create a terraform.tfvars.example file that shows an example of the content of the terraform.tfvars file with no sensitive values. This would serve as an example for users using your code.

Using Terraform Variables in Configuration

To use variables in your Terraform configurations, reference them using the syntax var.variable_name. For example, if you have a variable named street, you can reference it in your configuration with var.street like this:

data "dominos_address" "addr" {
  street      = var.street
}

Utilizing Built-in Functions

Terraform provides a variety of built-in functions that you can use to manipulate and transform variable values. These functions enable you to perform operations such as string manipulation, mathematical calculations, conditional logic, and more. Some common functions include lookup, merge, element, length, keys, and values.

Conditional Expressions

Conditional expressions in Terraform allow you to choose between two values based on a condition. The syntax for a conditional expression is

condition ? true_value : false_value

For example:

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

locals {
  should_order = var.order ? 1 : 0
}

resource "dominos_order" "order" {
  count      = local.should_order
...
}

The Terraform code example above defines a variable named "order" of type bool. It represents whether to order a pizza or not. The variable has a description stating its purpose, and a default value set to false if no value is explicitly provided.

The code also declares a local variable named "should_order" which is based on the value of the "order" variable. If the "order" variable is true, the "should_order" variable will be set to 1; otherwise, it will be set to 0. This local variable is used to conditionally control the creation of the "dominos_order" resource.

Working with Lists and Maps

Terraform variables support lists and maps, which are particularly useful when you need to handle multiple values or key-value pairs. Lists allow you to store and iterate over multiple elements, while maps provide a convenient way to associate values with specific keys. Let's take a look at an example:

# Example of a List
variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  default     = ["philly", "medium", "thin"]
}

# Example of a Map
variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
  default     = {
    number      = 123456789101112
    cvv         = 1314
    date        = "15/16"
    postal_code = "18192"
  }
}

You would then reference the list and map variables the same way with the var.variable syntax.

Terraform Environment Variables

Terraform environment variables are used to customize various aspects of Terraform's behavior, such as logging, input handling, and backend configuration. They can also be used to set values for input variables by using the TF_VAR_ prefix followed by the variable name.

How to Set Terraform Environment Variable Values

To set a Terraform environment variable value, use the export command on Unix/Linux systems or the set command on Windows systems. For example in Linux you would run:

export TF_VAR_first_name="Sam"

Input Variables

Input variables are variables that are provided when running Terraform commands. They allow users to input values interactively or through command-line arguments. These are the terraform variables that we've been discussing thus far.

Local Variables

Terraform also supports local variables, which are variables calculated based on other variables or expressions. Local variables can be useful for complex calculations or for simplifying configuration files by reducing redundancy. Below is an example of using local variables:

locals {
  customer_info = {
    first_name    = var.first_name
    last_name     = var.last_name
    email_address = "${var.first_name}.${var.last_name}@company.com"
    phone_number  = var.phone_number
  }
}

provider "dominos" {
  first_name    = local.customer_info.first_name
  last_name     = local.customer_info.last_name
  email_address = local.customer_info.email_address
  phone_number  = local.customer_info.phone_number
  credit_card   = var.credit_card
}

In this example, a local variable called customer_info is declared. This variable is of type map and contains as string values several key-value pairs.

The customer_info map includes the following key-value pairs:

  • first_name: The value is set to the value of the input variable var.first_name.
  • last_name: The value is set to the value of the input variable var.last_name.
  • email_address: The value is set to an interpolated string "${var.first_name}.${var.last_name}@company.com". This creates an email address by concatenating the values of the input variables var.first_name and var.last_name, separated by a dot and followed by "@company.com". This is one of the benefits of using locals. You can't combine input variables and assign the result to another input variable.
  • phone_number: The value is set to the value of the input variable var.phone_number.

We then call on the local values using the local.variable_name reference inside the provider block. When calling first_name for example using local.customer_info.first_name.

Output Variables

Output variables are used to export values from your Terraform configuration, making them available for use in other configurations or for display in the CLI output. They are defined using the output block and are typically defined in an outputs.tf file. Below is an example of an output variable for our pizza order:

output "OrderOutput" {
  value = data.dominos_menu_item.item.matches[*]
  description = "My order"
}

Variable Precedence

Terraform uses a specific order of precedence when determining the value of a variable. If the same variable is assigned multiple values, Terraform will use the value of highest precedence, overriding any other values. Below is the precedence order starting from the highest priority to the lowest.

  1. Environment variables (TF_VAR_variable_name)
  2. The terraform.tfvars file
  3. The terraform.tfvars.json file
  4. Any .auto.tfvars or .auto.tfvars.json files, processed in lexical order of their filenames.
  5. Any -var and -var-file options on the command line, in the order they are provided.
  6. Variable defaults

Variable Validation

You can add validation rules to your variables using the validation block within the variable definition. This allows you to enforce constraints on the values that can be assigned to a particular variable name. Check the example below:

variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  validation {
    condition     = contains(var.menu_item, "thin")
    error_message = "You must order a 'thin' pizza crust since it's our team's favorite"
  }
}

The example code above includes a validation block to enforce a specific condition on the menu_item variable value.

The validation block includes the following elements:

  • condition: This element specifies the condition that needs to be satisfied for the variable value to be considered valid. In this case, the condition is defined as contains(var.menu_item, "thin"). It checks whether the list of menu items contains the string "thin". If this condition is true, the validation passes.
  • error_message: This element provides an error message to be displayed if the validation condition is not met. In this case, the error message is "You must order a 'thin' pizza crust since it's our team's favorite".

Below is a screenshot of the error message if we don't include the "thin" element in the menu_item list variable:

menu_item    = ["philly", "medium"]

Sensitive Variables and Security

Sensitive Variables

Terraform allows you to mark variables as sensitive, which prevents their values from being displayed in the CLI output. To mark a variable as sensitive, add the sensitive attribute to the variable definition. Below is an example of our credit card to be used.

# variable definition contained in the variables.tf file
variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
}
# variable assignment contained in the terraform.tfvars file
credit_card = {
  number      = 123456789101112
  cvv         = 1314
  date        = "15/16"
  postal_code = "18192"
}

Typically you would not check the terraform.tfvars file into git since it usually contains sensitive values as mentioned before.

If you try to output the credit card variable, using this output block:

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
}

you'll get the error message below:

You would need to update the output variable file to look like this:

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
  sensitive = true
}

Now when you run terraform apply, you won't get an error and the output variable credit_card will show as <sensitive>

State File Shows Sensitive Data

Although the output of the credit card is not displayed, the sensitive data is still stored in the Terraform state file and therefore needs to be properly secured. You can use a remote backend that is encrypted at rest such as an S3 bucket or make use of a fully-fledged Terraform Automation & Collaboration Software (TACoS) solution such as env0. Below is the credit card data in the state file:

Our Full Example

Now let's take a look at the full example of ordering a Domino's Pizza. Below are all the necessary files to get this going. You will need to run the following Terraform commands to order a pizza. However, be careful putting in your actual credit card because the system will order a pizza for you!

terraform init
terraform apply

The variable "order" is set to false by default. This will prevent the resource "dominos_order" from actually ordering a pizza. If you are serious about ordering a pizza with Terraform, set this variable to true.

Our main.tf File

terraform {
  required_providers {
    dominos = {
      source  = "MNThomson/dominos"
      version = "0.2.1"
    }
  }
}

provider "dominos" {
  first_name    = local.customer_info.first_name
  last_name     = local.customer_info.last_name
  email_address = local.customer_info.email_address
  phone_number  = local.customer_info.phone_number
  credit_card   = var.credit_card
}

data "dominos_address" "addr" {
  street      = var.street
  city        = var.city
  region      = var.region
  postal_code = var.postal_code
}

data "dominos_store" "store" {
  address_url_object = data.dominos_address.addr.url_object
}

data "dominos_menu_item" "item" {
  store_id     = data.dominos_store.store.store_id
  query_string = var.menu_item
}

resource "dominos_order" "order" {
  count      = local.should_order
  api_object = data.dominos_address.addr.api_object
  store_id   = data.dominos_store.store.store_id
  item_codes = data.dominos_menu_item.item.matches[*].code
}

Our variables.tf File

variable "first_name" {
  type        = string
  description = "Customer's first name"
}

variable "last_name" {
  type        = string
  description = "Customer's last name"
}

variable "phone_number" {
  type        = string
  description = "Customer's phone number"
}

variable "street" {
  type        = string
  description = "Street address of the Store"
}

variable "city" {
  type        = string
  description = "City where the store is located"
}

variable "region" {
  type        = string
  description = "State or Province where the store is located"
}

variable "postal_code" {
  type        = string
  description = "Postal code of the Store"
}

variable "order" {
  type        = bool
  description = "Whether to order a pizza or not"
  default     = false
}

variable "menu_item" {
  type        = list(string)
  description = "A list of the items to order from the menu"
  validation {
    condition     = contains(var.menu_item, "thin")
    error_message = "You must order a 'thin' pizza crust since it's our team's favorite"
  }
}

variable "credit_card" {
  type        = map(string)
  description = "Credit Card Info"
  sensitive   = true
}

locals {
  customer_info = {
    first_name    = var.first_name
    last_name     = var.last_name
    email_address = "${var.first_name}.${var.last_name}@company.com"
    phone_number  = var.phone_number
  }
  should_order = var.order ? 1 : 0
}

Our terraform.tfvars File

credit_card = {
  number      = 123456789101112
  cvv         = 1314
  date        = "15/16"
  postal_code = "18192"
}

Our more_variables.auto.tfvars File

first_name   = "Sam"
last_name    = "Gabrail"
phone_number = "15555555555"
street       = "123 Main St"
city         = "Anytown"
region       = "WA"
postal_code  = "02122"
menu_item    = ["philly", "medium", "thin"]

Our outputs.tf File

output "OrderOutput" {
  value = data.dominos_menu_item.item.matches[*]
  description = "My order"
}

output "credit_card" {
  value = var.credit_card
  description = "Credit Card Info"
  sensitive = true
}

Tips and Best Practices

  • Use a consistent naming convention for variables and provide descriptive variable descriptions to improve code readability.
  • Store variable declarations in a separate variables.tf file.
  • Use reasonable default values for optional variables.
  • Consider using local variables and built-in functions to simplify your configurations and perform complex operations.
  • Use validation rules to enforce constraints on variable values.
  • Mark sensitive variables as such to prevent accidental exposure.
  • Consider using a TACoS (Terraform Automation and Collaboration Software) such as env0 where you can securely store your sensitive variables and manage those in hierarchy.

Advanced Terraform Variable Scenarios

Terraform supports complex data types, such as objects and tuples, as variables. These can be used to store and manipulate more complex data structures in your configurations.

Key Takeaways for Working with Terraform Variables

  • Terraform variables are essential for managing large infrastructure configurations and maintaining consistency across environments.
  • Variables can be assigned in various ways, including command-line flags, environment variables, and .tfvars files.
  • Use built-in functions, locals, conditional expressions, and complex data types to create more flexible and powerful configurations.
  • Follow best practices for naming, organizing, and securing your variables to ensure maintainable and secure infrastructure.

No items found.
Manage IaC at scale, with confidence 

Use custom workflows to model any process

Visualize all IaC changes pre and post-deployment

Gain code-to-cloud visibility and governance

Improve developer experience and collaboration

Sam Gabrail

Sam Gabrail is the President of TeKanAid Solutions Inc., an IT training company focused on DevOps. Sam enjoys creating content to help practitioners upskill in DevOps principles and cloud-native technologies.

Share this post
Logo

Manage IaC with confidence 

Use custom workflows to model any process

Visualize all changes pre and post-deployment

Gain code-to-cloud visibility and governance

Improve developer experience and collaboration

Start Free Trial
See what env0 can do for you

env0 is the best way to deploy, scale, and manage your Terraform and other Infrastructure as Code tools.

Milo waving