Terraform is not just another open source tool to manage infrastructure. One can define whole infrastructure components and implement the same using a specific coding language called Hashicorp Code Language (HCL). It has a full coding structure within itself, along with its own syntax and terminologies.
For this post, I will focus on a specific, important part of the language. All coding languages have a fundamental concept of ‘variables’ and ‘scopes’. Terraform also has its own version of this called Terraform Locals. There is also a concept of variables on Terraform which can be used to assign dynamic values, but we won’t be covering that in this post. I will instead explain locals and why (and how) they should be used in your Terraform scripts.
Terraform Locals are named values which can be assigned and used in your code. It mainly serves the purpose of reducing duplication within the Terraform code. When you use Locals in the code, since you are reducing duplication of the same value, you also increase the readability of the code.
If you want to compare Terraform Local to a general programming language construct, it will be equivalent to a local temporary variable declared within a function. Here is an example of local declaration in a Terraform script:
locals {
bucket_name = "${var.text1}-${var.text2}"
}
How does Terraform local differ from a Terraform variable?
The first difference can be pointed towards the scope. A Local is only accessible within the local module vs a Terraform variable, which can be scoped globally.
Another thing to note is that a local in Terraform doesn’t change its value once assigned. A variable value can be manipulated via expressions. This makes it easier to assign expression outputs to locals and use that throughout the code instead of using the expression itself at multiple places.
Now, let’s move on to see how to use a Terraform local. When you use a Terraform local in the code, there are two parts to it:
- First, declare the local along and assign a value
- Then, use the local name anywhere in the code where that value is needed
locals {
bucket_name = "mytest"
env = "dev"
}
Here, we are assigning two local values. There can be many locals defined within the locals section. These locals are assigned the values to the right. Now, the locals’ name can be used across the code.
The values assigned to the locals are not limited to just strings or constants. Expression outputs can also be assigned.
locals {
instance_ids = concat(aws_instance.ec1.*.id, aws_instance.ec3.*.id)
}
Locals can be assigned maps as values. Maps are nothing but a list of key value pairs. Here is an example of assigning a map to the local:
locals {
env_tags = {
envname = "dev"
envteam = "devteam"
}
}
Read more about the Terraform map variable.
One thing to note here is that the local name defined has to be unique across the code, because this is the name with which it will be referenced in various places.
In the example below, we want to add a prefix to all of the elements of a list, so, to do that, we are cycling through all of the elements with a for loop and leveraging the format function to add that prefix:
locals {
prefix_elements = [for elem in ["a", "b", "c"] : format("Hello %s", elem)]
}
locals {
even_numbers = [for i in [1, 2, 3, 4, 5, 6] : i if i % 2 == 0]
}
In the example above, we are using a local variable to hold the value of an expression. We are cycling through a list of numbers and creating a new list using only the even numbers from that list.
Now that we have seen how to declare a local, let’s see how to use one. Since the local has been defined with a specific name, the local can be referenced anywhere with the expression
local.<declared_name>
Make sure to take into account the fact that the declare block for local is “locals”, and the reference usage is “local”. I have seen this mistake happen many times, so this is something to be careful of.
Now let’s see an example of the usage. I will use the same local defined above for the s3 bucket name, to deploy an s3 bucket:
resource "aws_s3_bucket" "my_test_bucket" {
bucket = local.bucket_name
acl = "private"
tags = {
Name = local.bucket_name
Environment = local.env
}
}
Here it can be seen that the bucket name has been built using the local name which was defined earlier in an example. When deploying, the local expression will be replaced with the actual value from the declaration block.
Instead of being used as a static value block as above, it can also be used as an expression to replace a part of a longer text. Here is an example where a local is being used to specify a part of the bucket name:
resource "aws_s3_bucket" "my_test_bucket" {
bucket = "${local.bucket_name}-newbucket"
acl = "private"
tags = {
Name = local.bucket_name
Environment = local.env
}
}
A local variable is very similar to a variable in a programming language and also pretty similar to Terraform variables themselves, but the key difference is the fact that you cannot set them from an input, nor can you add their value in the .tfvars file. They are mainly used to manipulate information from another Terraform component, like a resource or a data source, and you can easily produce more meaningful results.
In a local variable, you can use dynamic expressions, a thing that you cannot do with input variables, and they can also be used with functions to provide, for example, the capability of reading variables from a YAML file or simply to populate a template.
As a best practice, they should be stored in a “locals.tf” file, but depending on the size of the configuration or module you are building, you can add them in “main.tf” or other files too. You can also have multiple locals blocks defined in the same configuration or module, Terraform will handle them out for you, but you cannot have multiple local variables with the same name, even though they are in a different locals block.
Here a variable is declared in the variables.tf file in a Terraform module:
variable "bucket_prefix" {
type = string
default = "mybucketname"
}
This variable can be used as a default value to the local in the Terraform script:
locals {
bucket_name = "${var.bucket_prefix}-bucket1"
}
resource "aws_s3_bucket" "my_test_bucket" {
bucket = local.bucket_name
acl = "private"
}
As you can see, a local can be easily combined with a Terraform variable to create complicated default value expressions for the Terraform local. This is very useful for scenarios where the local is needed to be made more dynamic based on input variable values.
Here are some examples of using Terraform locals in multiple scenarios.
The following script declares a Terraform local to define default tag values for Terraform resources. This value is used throughout the script. Here, an IAM role is being created with the tag local used:
locals {
resource_tags = {
project_name = "mytest",
category = "devresource"
}
}
resource "aws_iam_role" "myrole" {
name = "my_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "s3.amazonaws.com"
}
},
]
})
tags = local.resource_tags
}
If you want to know more about destroying resources from Terraform, see: How to Destroy Resources from Terraform: Tutorial and Examples
In this below example, the local value is being merged with a Terraform variable value. The merged value is used in the script. The output value is also defined to output the tag values:
variable "res_tags" {
type = map(string)
default = {
dept = "finance",
type = "app"
}
}
locals {
all_tags = {
env = "dev",
terraform = true
}
applied_tags = merge(var.res_tags, local.all_tags)
}
resource "aws_s3_bucket" "tagsbucket" {
bucket = "tags-bucket"
acl = "private"
tags = local.applied_tags
}
output "out_tags" {
value = local.applied_tags
}
Check out more examples with the Terraform merge function.
This post should give you a good perspective of using Locals in Terraform. It is recommended to use Locals generously in the code to avoid repetitions in the code. Additionally, the locals will help make the code more readable. All the best with your Terraform explorations!
If you need any help managing your Terraform infrastructure, building more complex workflows based on Terraform, and managing AWS credentials per run, instead of using a static pair on your local machine, Spacelift is a fantastic tool for this. See how Spacelift can help you with storing complex variables. Check it for free by creating a trial account.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Terraform Management Made Easy
Spacelift effectively manages Terraform state, more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and includes many more features.