Terraform is an infrastructure as code tool that lets you define cloud and on-premises resources in human-readable configuration files that you can version, reuse and share.
It can manage low-level components like compute, storage and networking resources, as well as high-level components like DNS entries and SaaS features. To do all this, Terraform uses HashiCorp configuration language (hcl).
Terraform Environment Variable Explained
Because we have to manage different instances of infrastructures living inside different environments like development, quality assurance (QA) or staging, it’s critical to provide the environmental context to your configuration without repeating the same code or polluting it with environment-specific hardcoded details. Terraform environment variables play a critical role in providing this abstraction.
Let’s dive into what environment variables are and how and when to use them.
What Is a Terraform Environment Variable?
Environment variables are like parameters or arguments of a function. They can be defined in a separate file and used throughout your configuration. The purpose of a Terraform tool is to have the same infrastructure provisioning across environments.
Still, there are a few things that will vary from one environment to another, such as database credentials, DNS, etc. So, instead of hardcoding these values and maintaining multiple copies of almost similar configuration, Terraform allows us to parameterize our configuration using environment variables.
How to Define Terraform Environment Variables
In order to use them you need to define them first inside a file somewhere in your configuration. The syntax for defining it is, as below:
Definition
variable <name> {
description = <optional description>
type* = <type of variable>
default = <optional default value>
}
Example
variable "env" {
description = "System environment name"
type = string
default = "development"
}
This block syntax defines a new variable by giving it a name, and the type keyword specifies its type. We can provide an optional description and default value (optional) using description and default keywords.
Terraform supports various types which can be categorized, as follows:
1. Primitive Types: Like string
, number
and bool
.
Definition:
variable “instance_type” {
type = string
default = “t2.micro”
}
Usage:
resource "aws_instance" "my-instance" {
...
"instance_type": var.instance_type,
...
}
2. Complex Types: Like list
or map
Definition:
variable “user_names” {
type = list(string)
default = [“user-1”, “user-2”]
}
Usage:
resource "aws_iam_user" "iam-users" {
…
count = length(var.user_names)
name = var.user_names[count.index]
}
Definition:
variable “amis” {
type = map
default = {
“us-east-1” = “ami-z9aspap”
“us-west-1” = “ami-8sdsoi1”
}
}
Usage:
resource "aws_instance" "my-ec2-instance" {
…
ami = var.amis[var.region]
instance_type = “t2.micro”
}
3. User-defined types: Like object
.
Definition:
variable “rg_config” {
type = object({
create_rg = bool
name = string
location = string
})
}
Usage:
resource "azurerm_resource_group" "demo_rg" {
…
count = var.rg_config.create_rg ? 1 : 0
name = var.rg_config.name
location = var.rg_config.location
…
}
How to Use Terraform Environment Variables
Once you’ve defined a variable you can use or reference that variable in any part of your configuration. The syntax is, as follows:
Usage/Reference
var.<name>
Example
resource "aws_ecs_task_definition" "my-app-task" {
...
"image": "${var.env}.dkr.ecr.ap-south-1.amazonaws.com/${var.env}-my-app",
...
}
At run time, Terraform will replace var.name
with its value.
How to Set Terraform Environment Variable Values
The purpose of the environment variables is to provide the abstraction between the core infrastructure components, and their various environment-specific instances. So, we must set the values of these variables outside of the configuration at the time of plan/apply.
There are multiple ways to set the environment variables and each one has a different use case. So it’s not about picking one over another but using all these options in conjunction wherever applicable.
- Provide the default value in the variable definition itself using the default keyword: This is a great way to provide sensible defaults when one exists. But we should never use this option if the resource is environment specific and must be separate for each environment.
- Via a Terraform prompt at the time of apply/plan command: If no default value is present for the variable, Terraform will interrupt the apply/plan command for user input for these values. This option is used when we are actually testing the configuration and playing out with different values. The downside of using it in a production environment is that it may result in different infrastructure for each run, and doesn’t guarantee consistency.
- Via shell variables with
TF_VAR_
prefix: Terraform will read shell variableTF_VAR_env=production
and will set the valueproduction
to the variableenv
. This method is mostly used in CI/CD. This is a great way to provide environment secrets that must not be stored in your code repository in plain text format. - Via
*.tfvars file
: We can have multiple files one for each environment likestaging.tfvars
andproduction.tfvars
, and at the time of plan/apply, we can provide the path of the appropriate file using the-var-file
option. This option is good when we have to set several environment variables, and the values aren't sensitive and can be stored in the code repository. - Via
-var option
at the time of plan/apply command: This is an alternative way to provide values to the environment variables. This is useful when there aren’t many variables, and we are applying the configuration in a non-interactive fashion like CI/CD or through a script.
Terraform Environment Variable Use Cases
There are two major use cases for terraform environment variables
- To avoid code duplication for each environment.
- To store sensitive values outside the configuration, such as a database password.
Other Terraform Variables to Know
As with any other programming language, HCL also supports variables to make configuration more flexible and modular.
Local Variables
Local variables are like named constants defined in a program, they allow us to define some value that is needed in various parts of the configuration, and if that value changes we don’t have to make changes in all the places but only once.
The syntax for defining local variables is, as follows:
Definition
locals {
<variable_name_1> = <variable_value_1>
<variable_name_2> = <variable_value_2>
}
Example
locals {
algorithm = "aws:kms"
availability_zone = "US-East-1"
}
This block syntax defines local variables as a name-value pair.
Usage/Reference
Once defined, you can use/reference local variables using the below syntax
local.<variable_name>
Example
apply_server_side_encryption_by_default {
sse_algorithm = local.algorithm
}
While defining, it is “locals” (plural), but at the time of reference, it’s “local.<var_name>” (singular).
Output Variables
Output variables are like the return value of a statement. They allow us to store the values that terraform operations return, usually the ones which are not input and are generated by the back end system, like resource ID.
We can then use these values to pass on to other parts of the configuration or can be read outside the terraform config. There is also an option to mark these variables as sensitive and terraform will mask the value by default while yanking the output on the console and we need to read these values using terraform output command explicitly.
The syntax for defining output variables is as follows
Definition
output <name> {
value* = <terraform_operation_return_statement>
sensitive = <optional true|false>
}
Example
resource "aws_iam_access_key" "iam-key" {
user = aws_iam_user.admin-user.name
}
output "iam-key" {
value = aws_iam_access_key.iam-key
sensitive = true
}
This block syntax defines a sensitive output variable as a storage for the return value of the terraform config statement.
Usage/Reference
$ terraform output <name>
Example
terraform output iam-key
The above command will dereference the output variable and will return the actual return value of the original Terraform statement.