Terraform Local Variables

Brendan Thompson • 27 September 2022 • 8 min read

What are local variables?#

Local variables are blocks of code within Terraform that allow us to store static pieces of data that we might want to refer to at a later date, or deeply dynamic pieces of data that can be manipulated by the state of our resources, data sources, and information provided by our input variables or even other local variables. They are incredibly powerful! In my opinion, however, they are far too commonly used to store large amounts of static configuration that should be passed into the code through the use of input variables.

Below is an example of a local variable block:

locals {
  cat_sound = "meow"
}

As can be seen above we have declared a variable called cat_sound and assigned it the value meow we would access this value by calling the variable like so local.cat_sound. One thing that makes local variables different from input variables is the fact that their types are implicit rather than explicit.

Static Local Variables#

Static local variables are very much so like the above where the local block we are declaring contains the value alongside the variable name, this is supremely useful when you're storing properties that don't change. I like to think of locals when used in this manner as constants, this means we should not mutate the variable at all and it should be very rarely updated or not at all.

If you find yourself storing your configuration - think the explicit configuration of a virtual machine - in local variables you should be rethinking your configuration ingestion strategy. (I would suggest having a read of Terraform Configuration Ingestion)

Let's have a look at a proper example and consumption of local variables used in this manner.

locals {
  location = "australiaeast"
  storage_account = {
    tier             = "Standard"
    replication_type = "ZRS"
    tls              = "1.2"
  }
}

resource "azurerm_resource_group" "this" {
  name     = "rg-mom-spells"
  location = local.location
}

resource "azurerm_storage_account" "this" {
  name                = "samomspells"
  resource_group_name = azurerm_resource_group.this.name
  location            = azurerm_resource_group.this.location

  account_tier             = local.storage_account.tier
  account_replication_type = local.storage_account.replication_type
  min_tls_version          = local.storage_account.tls
}

In the above example, we are creating an Azure resource group and a storage account for the Ministry of Magic to store its spells reference. The cloud engineers there have some requirements around what tier, replication type and tls version can be used when provisioning the storage account, as well as the location where these resources can be provisioned. Having these properties statically set in the local variables block ensures that they cannot be changed without going through a Pull Request. If this was baked into a module then any consumer of the module would have no ability to change these static properties. These act as constants for our code.

Dynamic Local Variables#

Dynamic local variables are where things become more fun, and useful. If you were passing in some configuration through input variables and you needed to mutate them based on some context within your code then using locals to do that mutation is a wise decision.

In the following example, we are going to be using input variables to lookup our true values which are defined within a locals block.

variable "location" {
  type        = string
  description = <<DESC
    (Required)  The location in which the resources will be provisioned.
  DESC

  validation {
    condition     = contains(["syd"], var.location)
    error_message = "Err: The location must be 'syd'."
  }
}

variable "cloud" {
  type        = string
  description = <<DESC
    (Required)  The cloud that the resources will be deployed to.
  DESC

  validation {
    condition     = contains(["azure", "aws", "gcp"], var.cloud)
    error_message = "Err: valid cloud options are: 'azure', 'aws', 'gcp'."
  }
}

locals {
  location = {
    syd = {
      azure = "australiasoutheast"
      aws   = "ap-southeast-2"
      gcp   = "australia-southeast1"
    }
  }
}

resource "scratch_string" "this" {
in = format("Cloud: %s, Location: %s",
var.cloud,
local.location[var.location][var.cloud]
)
}

In the above example, we have a single local block called location (in a real-world scenario this wouldn't be a single value, it would likely be several options), with this, we will be able to find the cloud-specific region name based on a common name. On the highlighted line we are using [] to get the location with an input variable for location and cloud. If we were to write it out fully without using variables it would look like this:

//                 azure      syd
local.location[var.cloud][var.location]
// Returns: 'australiaeast'

The above allows us to retrieve simple data based on one or more pieces of context, in this instance location and cloud. Below is the same logic we are just looking up the values a little differently. Both ways work perfectly well the below is just a little clearer in my opinion.

resource "scratch_string" "this" {
  in = format("Cloud: %s, Location: %s",
    var.cloud,
lookup(
lookup(local.location, var.location, ""),
var.cloud, ""
)
) }

Other ways that we can use locals are as follows:

All of these can be extremely powerful, and useful when we are writing our Terraform code. However as I have said many times in other posts great power comes with great responsibility. The more locals we use the more complex our code gets to read and comprehend. If you are using a lot of dynamic local variables as we have discussed here then you should document what they are for so that future engineers understand what is going on.

Closing Out#

Local variables are a critical tool to have in your Terraform toolbox and something that you should have a solid grasp on. They can be used to store static pieces of information like a constant in a programming language, or they can be used to mutate data and/or concatenate pieces of data. Both are equally useful.

Local variables can become dangerous when there are too many and the reasons for why aren't well documented, or when you're using them to store all the configurations that Terraform is used to provision resources. If you notice you are storing a lot of configuration in local variables it might be a good opportunity to assess the use of a module or ingesting configuration via another method.

When declaring local variables you should:

Hopefully, this has given some good insights into local variables!


Brendan Thompson

Principal Cloud Engineer

Discuss on Twitter