Terraform locals are named values that you can refer to in your configuration. You can use local values to simplify your Terraform configuration and avoid repetition. Local values (locals) can also help you write more readable configuration by using meaningful names rather than hard-coding values.
Unlike variables found in programming languages, Terraform’s locals don’t change values during or between Terraform runs such as plan, apply, or destroy. You can use locals to give a name to the result of any Terraform expression, and re-use that name throughout your configuration. Unlike input variables, locals are not set directly by users of your configuration.
In this tutorial, you will use Terraform to deploy a web application on AWS. The supporting infrastructure includes a VPC, load balancer, and EC2 instances. You will then use local values to reduce repetition in the configuration, and then combine local values with input variables to require a minimal set of resource tags while still allowing for user customization.
»Prerequisites
In order to follow this tutorial, you will need the following:
- The Terraform CLI, version 0.13 or later.
- AWS Credentials configured for use with Terraform.
- The git CLI.
Note: Some of the infrastructure in this tutorial may not qualify for the AWS free tier. Destroy the infrastructure at the end of the guide to avoid unnecessary charges. We are not responsible for any charges that you incur.
»Create infrastructure
Clone the Learn Terraform locals GitHub repository for this tutorial.
$ git clone https://github.com/hashicorp/learn-terraform-locals.git
Change to the repository directory.
$ cd learn-terraform-locals
The configuration in main.tf
defines a web application, including a VPC,
load balancer, and EC2 instances.
Initialize this configuration.
$ terraform init
Now apply the configuration.
$ terraform apply
Respond to the confirmation prompt with a yes
to create the example
infrastructure.
»Use locals to name resources
In the configuration's main.tf
file, several of the resources have name
arguments created by interpolating the project
and environment
values from
the resource_tags
variable with another value that describes the resource.
Reduce duplication and make this configuration clearer by setting the shared
part of each name
as a local value to re-use across your configuration.
Define the local name_suffix
by pasting the following snippet at the top of
main.tf
.
locals {
name_suffix = "${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
}
As in any Terraform configuration, the order in which configuration appears does not affect how Terraform interprets the configuration. To make it easier for people to understand your configuration, you may want to include local definitions near the top of your configuration files.
Now update the name
attributes in the configuration to use this new local
value.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.44.0"
- name = "vpc-${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
+ name = "vpc-${local.name_suffix}"
# ...
module "app_security_group" {
source = "terraform-aws-modules/security-group/aws//modules/web"
version = "3.12.0"
- name = "web-sg-${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
+ name = "web-sg-${local.name_suffix}"
# ...
module "lb_security_group" {
source = "terraform-aws-modules/security-group/aws//modules/web"
version = "3.12.0"
- name = "lb-sg-${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
+ name = "lb-sg-${local.name_suffix}"
# ...
module "elb_http" {
source = "terraform-aws-modules/elb/aws"
version = "2.4.0"
# Ensure load balancer name is unique
- name = "lb-${random_string.lb_id.result}-${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
+ name = "lb-${random_string.lb_id.result}-${local.name_suffix}"
# ...
Take note that the load balancer name includes a random string so that the names will be unique, which is a requirement for load balancer names in AWS.
Apply the configuration to verify that the names' values haven't changed.
$ terraform apply
random_string.lb_id: Refreshing state... [id=Rjv]
data.aws_ami.amazon_linux: Refreshing state... [id=ami-0528a5175983e7f28]
data.aws_availability_zones.available: Refreshing state... [id=2020-10-19 20:36:41.151513 +0000 UTC]
# ...Output truncated
Plan: 0 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
data.aws_availability_zones.available: Reading... [id=2020-10-20 15:53:22.430434 +0000 UTC]
data.aws_availability_zones.available: Read complete after 0s [id=2020-10-20 15:54:23.940292 +0000 UTC]
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
public_dns_name = lb-Rjv-my-project-dev-775803674.us-west-2.elb.amazonaws.com
»Combine variables with local values
Unlike variable values, local values can use dynamic expressions and resource
arguments. The resource_tags
map in variables.tf
defines the tags for the
local name_suffix
as defaults. A user could override the default value for
this map and omit the project_name
and environment
tags.
Many projects require that all resources are tagged in a certain way to track them. To enforce this requirement, use a local value in combination with variables so the tags assigned to resources include at least the project name and environment.
Add the following new variable definitions to variables.tf
.
variable project_name {
description = "Name of the project."
type = string
default = "my-project"
}
variable environment {
description = "Name of the environment."
type = string
default = "dev"
}
Next, change the default value of the resource_tags
variable to an empty map.
variable resource_tags {
description = "Tags to set for all resources"
type = map(string)
- default = {
- project = "my-project",
- environment = "dev"
- }
+ default = { }
}
Add a new locals
block to main.tf
to create a map combining both required
tags and user defined tags.
locals {
required_tags = {
project = var.project_name,
environment = var.environment
}
tags = merge(var.resource_tags, local.required_tags)
}
All of your configuration's local values can be defined in a single locals
block, or you can use multiple blocks.
Update the definition of the name_prefix
local to use the new variables for
project_name and environment.
locals {
- name_suffix = "${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
+ name_suffix = "${var.project_name}-${var.environment}"
}
Finally, update the five references to tags in main.tf
to use the new local
value.
- tags = var.resource_tags
+ tags = local.tags
# ... replace all five occurrences of `tags = var.resource_tags`
Now, apply these changes to see that the tags for each resource are a
combination of the required_tags
local value and the resource_tags
variable.
Once again, because none of the values of changed, Terraform will show no changes to
apply.
$ terraform apply
random_string.lb_id: Refreshing state... [id=Rjv]
data.aws_availability_zones.available: Refreshing state... [id=2020-10-20 15:54:23.940292 +0000 UTC]
data.aws_ami.amazon_linux: Refreshing state... [id=ami-0528a5175983e7f28]
# ...Output truncated
Plan: 0 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
data.aws_availability_zones.available: Reading... [id=2020-10-20 17:15:58.741635 +0000 UTC]
data.aws_availability_zones.available: Read complete after 0s [id=2020-10-20 17:16:43.88493 +0000 UTC]
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
public_dns_name = lb-Rjv-my-project-dev-775803674.us-west-2.elb.amazonaws.com
Next, run another apply, this time changing the environment from dev
to
prod
.
$ terraform apply -var "environment=prod"
random_string.lb_id: Refreshing state... [id=Rjv]
data.aws_ami.amazon_linux: Refreshing state... [id=ami-0528a5175983e7f28]
data.aws_availability_zones.available: Refreshing state... [id=2020-10-20 17:16:43.88493 +0000 UTC]
# ...Output truncated
Plan: 17 to add, 15 to change, 17 to destroy.
Changes to Outputs:
~ public_dns_name = "lb-Rjv-my-project-dev-775803674.us-west-2.elb.amazonaws.com" -> (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
# ...Output truncated
module.lb_security_group.module.sg.aws_security_group.this_name_prefix[0]: Destruction complete after 11s
module.app_security_group.module.sg.aws_security_group.this_name_prefix[0]: Destruction complete after 6s
Apply complete! Resources: 17 added, 15 changed, 17 destroyed.
Outputs:
public_dns_name = lb-Rjv-my-project-prod-1960581635.us-west-2.elb.amazonaws.com
Respond to the confirmation prompt with yes
, and Terraform will destroy and
recreate this infrastructure.
»Clean up your infrastructure
In this tutorial you defined and used Terraform local values to reduce duplication and improve readability. You also combined user-defined variables with locals to improve the consistency of your infrastructure tags.
Before moving on, destroy the infrastructure you created by running the
terraform destroy
command.
$ terraform destroy
Be sure to respond to the confirmation prompt with yes
.
»Next steps
Now that you know how to define and use variables, check out the following resources for more information.
- Read the Local variables documentation
- Learn how to create and use Terraform modules.