This blog post introduces variables and outputs in Terraform.

In the previous article, we discussed how easily we can set up a cloud infrastructure with Terraform using a simple example. Although we could already observe the usefulness of Terraform as an IaC tool from that example, many parameters were hardcoded, making the code non-reusable, inflexible, and insecure, defying the important purposes of Terraform as an IaC tool. Hence, in this article, we will discuss how we can make use of variables and outputs in HCL to start taking more advantage of Terraform.
Variables
In HCL, we can define variables using a variable
block, containing a description, type,
and optionally a default value and whether the variable is sensitive (for environment variables),
and use them with var.<var_name>
. While variables can be defined in the same main.tf
file
where resources are defined, it's best practice to set up a separate vars.tf
file for defining them.
The following is an example of variable definitions in vars.tf
.
variable "ami" {
description = "Amazon Machine Image for EC2 instance"
type = string
}
variable "instance_type" {
description = "EC2 instance type (default: t4.micro)"
type = string
default = "t4.micro"
}
variable "db_password" {
description = "SENSITIVE DB password"
type = string
sensitive = true
}
Instead of hardcoded strings, we can use variables like var.ami
and var.instance_type
at appropriate places.
In Terraform, there are many ways to provide values to variables. When performing plan
and apply
,
Terraform first checks for -var
and -var-file
arguments and verifies that values are provided
for all necessary variables. If no arguments are provided, or if there are insufficient values,
it looks for *.auto.tfvars
and terraform.tfvars
files ( tfvars
files should contain
<var_name> = <value>
on each row). Then, it checks for TF_VAR_<name>
environment variables
and then checks for default values. If there are still insufficient values, it will prompt us
to manually input values on the CLI.
Usually, we define values for non-sensitive variables in .tfvars
files (like ami
and instance_type
)
and sensitive variables (like db_password
, where the sensitive
parameter is set to true
to avoid
it being displayed on the CLI) in .env
files or as -var
arguments. By using this setup,
we can make the code reusable, flexible, and secure, as we can edit or use different .tfvars
files
depending on the infrastructure requirements and phases (development, staging, and production),
and avoid hardcoded secrets in files that are potentially shared in a repository.
Local Variables & Outputs
There are cases where we want to use variables not for code reusability but for better code organization
and to avoid repetition. For example, we might want to avoid hardcoding the port number 8080
for
the EC2 instance by creating a local variable ec2_port
. In such cases, we can make use of local variables,
which only apply within the file they are defined in and can be defined using a locals
block as follows.
locals {
ec2_port = 8080
lb_port_in = 80
lb_port_out = 0
}
All the local variables for a specific file are defined within the same locals
block, as shown above.
We can then use those variables like local.ec2_port
, local.lb_port_in
, and so on.
Local variables help avoid repetition and make edits easier. There are also cases where we want
to obtain the output values of attributes available after the apply
command,
such as when we use the IP address of EC2 instances for setting up networking (e.g., a Kubernetes cluster).
For these cases, we can use outputs defined by an output
block, containing a value
(optionally a description, type, and sensitive
parameter) that specifies which attribute of a resource we want.
Outputs are typically defined in a outputs.tf
file, similar to how variables are defined in vars.tf
.
output "instance_1_ip_addr" {
type = string
values = aws_instance.instance_1.public_ip
}
After using the apply
command and resources are ready, non-sensitive outputs are displayed on the CLI.
We can also use terraform output -json
to see all the parameters of the outputs and
terraform output <output_name>
to check the value of a specific output, which can be
logged or piped for subsequent manual or automatic infrastructure configurations.
Language Features & Meta-Arguments
Beyond variables and outputs, there are many language features that enable us to write better code.
For example, we can use template strings ("AMI used: ${var.ami}"
), operators (!
, ==
, etc.),
conditionals (<cond> ? true : false
), for loops ([for i in var.list: i]
), and so on in our resource definitions.
There are also useful predefined functions for hashing, type conversion, date and time, and so on,
and I highly recommend checking out the official documentation cited at the bottom of the article for
more.
We can also use meta-arguments in our resource blocks, which allow us to have more control over
our infrastructure provisioning. For example, we can use the depends_on
argument,
where we can define the resources that need to be created before the resource with this argument is built,
in a list. We also have access to the count
meta-argument, which allows us to create multiple replicas
of the same resources and refer to them individually using count.index
, the for_each
argument,
which allows us to iterate over a list (or a set) and create a resource for each,
referring to them with each.key
, and the lifecycle
argument, which allows us to configure
behaviors in the lifecycle (like create_before_destroy
for availability and ignore_changes
to avoid modifications).
There are many other useful meta-arguments in Terraform, so I recommend checking out the official documentation
for more details.
Conclusion
In this article, we introduced variables, local variables, outputs, and other useful language features and meta-arguments for taking advantage of Terraform as an IaC tool. I highly recommend using these features to rewrite the Terraform code from the previous article for practice and in other projects as well. For more details and explanations of these features, I highly recommend checking out the resources cited below.
Resources
- DevOps Directive. 2022. Complete Terraform Course - From BEGINNER to PRO! (Learn Infrastructure as Code). YouTube.
- HashiCorp. n.d. Terraform Language Documentation. Terraform.