How to Manage Terraform Locals
Tags: terraform, devops, cloud, cloudcomputing
Published: 2024-06-27 12:10:00

**What are Terraform Locals**
-----------------------------

All programming languages have a way to express and store values within the context of a code block. In the case of Terraform configurations, that functionality is delivered through Terraform local values. These allow you to define temporary values and then reference them elsewhere in the configuration.

Local values – often called “locals” or “Terraform local variables” – can be used to store an expression that will be referenced multiple times, perform data transformation from other sources, or store static values to be used in the configuration. 

Locals are one of three varieties of [Terraform variables](https://www.env0.com/blog/terraform-variables) that can be used to request or public values, with the other two being “input variables” and “output values.” In this post, we'll examine how to define local values, how they differ from input variables, and common uses for locals.

**How to Implement Terraform Locals**
-------------------------------------

Defining local values in Terraform code is done using a locals block, with each local assigned a name and value in the format of a key-value pair. The following code creates a local value called `environment` and assigns it the string value `development`.

    locals {
      environment = "development"
    }

Locals can be assigned any valid Terraform data type, such as a `string`, `list`, `map`, or `object`. The actual values can come from input variables, resource attributes, or other local values defined in the configuration.

    locals {
      environment = "development"
      server_list = ["web", "app", "db"]
      subnet_map  = {
        web = var.web_subnet
        app = var.app_subnet
        db  = var.db_subnet
      }
    } 

A `locals` block is also useful for composing expressions for values that will be used elsewhere in the configuration, including in both ternary and `for` expressions. The following example sets the DNS value to the load balancer `fqdn` if a load balancer is being created, and to the `fqdn` of a virtual machine otherwise.

    locals {
      dns_entry = var.create_load_balancer ? 
                    azurerm_public_ip.lb.fqdn : azurerm_public_ip.vm.fqdn
    }

A `for` expression could be used to create a local value for naming resources. The code below would add a naming prefix input variable to the beginning of each server in the list.

    locals {
      server_list = ["web", "app", "db"]
      server_names = [ for svr in local.server_list : "${var.prefix}-${svr}" ]
    }

The `locals` block can appear multiple times in a Terraform configuration, so long as each local value has a unique name within the configuration. 

Some organizations prefer to define all their locals in a single `locals` block stored in a dedicated **locals.tf** file, while others will use multiple locals blocks, placing each close to the resources and data sources referencing it.

The value stored in a local is referenced with the local keyword followed by the name of the local. The following code makes use of the `server_names` and `environment` locals in an Azure VM.

    resource "azurerm_linux_virtual_machine" "web" {
      name = local.server_names[0]
      #...
      tags = {
        environment = local.environment
      }
    }

Locals defined in a Terraform module are only available within the scope of that module. For instance, the local value named `environment` that is defined in the root module cannot be referenced directly by a child module. 

Additionally, locals defined in the child module are not directly available to the parent module. Locals follow the same scoping principles as input variables, resources, and data sources.

**Terraform Locals vs. Input Variables**
----------------------------------------

While Terraform input variables and local values might sound very similar, there are some key differences to keep in mind when selecting which construct to use.

### **Input Variables**

*   Dynamic values defined at run time
*   Values cannot come from other configuration sources
*   Default value can be overridden

### **Local Values**

*   Defined by an internal expression
*   Can be assigned any valid Terraform expression
*   Can be assigned static values

In most other programming languages, input variables would instead be called “parameters” and local values would simply be called “variables.”

If you are coming from another programming language, that may be a helpful mental framework to apply when thinking about whether to use an input variable or local value.

Generally, if you need to be able to change the value of a placeholder at runtime, you should use an input variable. If you simply need a reusable expression or need to perform data transformation, you should use a local value.

**Examples of Terraform Locals**
--------------------------------

Let's walk through a few common examples of how and when Terraform locals might be used.

### **1. Reusing a Value**

Locals allow you to define a value once and reuse it throughout the configuration. For instance, we can use a local value to define a set of common tags for your resources based on input variables.

    locals {
      common_tags = {
        environment = var.environment
        project     = var.project
        billing     = var.billingcode
      }
    }
    
    resource "azurerm_resource_group" "main" {
      ...
      tags = local.common_tags
    }

By defining the common tags in a `locals` block, it is simple to add a new tag to all resources with having to update every `tags` argument in every resource block.

Another common use case is establishing a standard naming convention for all resources in a configuration. The Cloud Posse [terraform-null-label module](https://registry.terraform.io/modules/cloudposse/label/null/latest) makes extensive use of locals for exactly that purpose.

### **2. Data Transformation**

Locals can also be used to transform data before it is passed on in the configuration. For instance, you could use a local variable and a for expression to update the values in a list.

    locals {
      env_config_list = [ for item in var.config_list : "${local.environment}-${item}"
    }

Even if the local value isn't going to be used multiple times, performing the data transformation in a `locals` block can help make other configuration blocks easier to read. Locals can also be used as an intermediary step in data transformation, to simplify code updates for future maintainers.

### **3. Constant Values**

Unlike input variables, locals don't accept input values, so they can be used to set static constants in the configuration that can only be updated by altering the code itself. For instance, the code below establishes default ports for a web application.

    locals {
      web_app_ports = ["8080", "8443"]
    }

If someone wishes to alter the port list, they will need to go through the code change process, rather than changing the input variable values submitted during a Terraform run. Using locals helps code maintainers view all the configuration values in a single place, while not allowing consumers of the configuration to change values easily.

**Conclusion**
--------------

Locals in Terraform configuration files construct reusable values to be referenced throughout a configuration. Values can be assigned to locals from input variables, resources, data sources, and other related local values.

Local values are a core component of Terraform and are also supported on the new open-source project [OpenTofu](https://opentofu.org/). OpenTofu is intended to be a drop-in replacement for existing Terraform deployments, and its support of locals is one example of its interoperability. You can join [OpenTofu’s Slack community](https://communityinviter.com/apps/opentfcommunity/opentofu) and check out how to [contribute](https://github.com/opentofu/opentofu/blob/main/CONTRIBUTING.md).