Subscribe to the Spacelift Blog newsletter, Mission Infrastructure |

Sign Up ➡️

Terraform

Terraform Locals: What Are They, How to Use Them

Terraform locals

Terraform locals are named, module-scoped values that let you assign an expression once and reuse it throughout your configuration — reducing duplication and making your code easier to read and maintain.

If you’ve ever found yourself repeating the same tag block, name prefix, or computed value across multiple resources, locals are the solution. Unlike input variables, locals can’t be overridden from outside the module, which makes them ideal for intermediate values derived from other resources, data sources, or expressions.

In this guide, you’ll learn:

  • What Terraform locals are and how they differ from input variables
  • How to declare and reference locals in your configuration
  • How to use locals with strings, lists, maps, for loops, and conditional expressions
  • How to combine locals with variables for dynamic values
  • Practical examples using AWS resources
  • Common locals errors

What are Terraform locals?

Terraform locals are named values that can be assigned and used in your code. They mainly serve 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 a local declaration in a Terraform script:

locals {
  bucket_name = "${var.text1}-${var.text2}"
}

When should you use Terraform locals vs. variables?

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 them throughout the code instead of using the expression itself at multiple places.

Locals are declared with the locals block (plural) but referenced with local.<name> (singular). This is the most common source of confusion — we’ll cover it in detail below.

How to use Terraform locals?

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

Simple string local

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.

Concatenating lists with a local

locals {
  instance_ids = concat(aws_instance.ec1.*.id, aws_instance.ec3.*.id)
}

Map locals

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.

Using for loop in a local

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)]
}

Using for loop and if in a local

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

  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"

  tags = {
    Name        = local.bucket_name
    Environment = local.env
  }
}

How to combine Terraform local with variables?

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 needs to be made more dynamic based on input variable values.

Terraform locals code examples

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 the example below, 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"

  tags = local.applied_tags
}

output "out_tags" {
  value = local.applied_tags
}

Check out more examples with the Terraform merge function.

Common locals errors

Here are the most frequent mistakes developers run into when working with Terraform locals and how to fix them.

  • Using local instead of locals in the declaration block. The block that declares your values is locals (plural), but you reference them with local.<name> (singular). Writing local { ... } instead of locals { ... } will throw an Unsupported block type error immediately.
  • Duplicate local names. Every local name must be unique within a module — even across multiple locals blocks. Terraform merges all locals blocks at plan time, so a name defined twice will cause a Duplicate local value error regardless of which file it lives in.
  • Circular references. A local cannot reference itself or form a dependency loop with another local. If local.a references local.b and local.b references local.a, Terraform will throw a Cycle error during the graph evaluation phase.
  • Referencing a local before it makes sense in context. Locals are evaluated lazily, but they can only reference values that are known at plan time. Referencing a resource attribute that isn’t known until apply (like a generated ID) inside a local will produce an Unknown value error or unexpected (known after apply) output.

How to manage Terraform resources with Spacelift

Terraform is really powerful, but to achieve an end-to-end secure GitOps approach, you need a platform built for infrastructure orchestration. Spacelift goes beyond running Terraform workflows, giving you a governed, two-path deployment model and unlocking features such as:

  • Policy as code (based on Open Policy Agent) — Control how many approvals you need for runs, what kind of resources you can create, and what parameters those resources can have. You can also govern behavior when a pull request is open or merged.
  • Multi-IaC orchestration — Combine Terraform with Kubernetes, Ansible, and other infrastructure as code tools such as OpenTofu, Pulumi, and CloudFormation. Create dependencies between them and share outputs across stacks.
  • Governed developer self-service — Use Blueprints and Templates to build Golden Paths for your teams. Complete a simple form to provision infrastructure based on Terraform and other supported tools — with guardrails enforced throughout.
  • AI-powered visibility – Use Spacelift Intelligence to surface actionable insights across your stacks, runs, and resources, helping you identify issues faster and make better infrastructure decisions.
  • Integrations with third-party tools — Connect your existing tools and build policies around them. For example, see how to integrate security tools into your workflows using Custom Inputs.

Spacelift also supports private workers, so you can execute infrastructure workflows inside your own security perimeter. See the documentation for details on configuring worker pools.

You can try it for free by creating a trial account or booking a demo with one of our engineers.

Key points

Terraform locals are one of the most practical tools in HCL — use one whenever you find yourself writing the same expression or value in more than one place, keeping in mind that they are module-scoped and can’t be overridden from outside, which makes them the right choice for values derived internally rather than passed in by a caller.

Store them in a dedicated locals.tf for larger modules, avoid trivial aliases that add indirection without any real transformation, and if you need tighter control over how locals and variables flow across modules and workspaces at scale, Spacelift’s free trial is a good place to start.

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.

Start free trial

Frequently asked questions

  • What is the difference between locals and tfvars?

    Locals are computed constants inside a module, derived from expressions and not overrideable by callers, useful for keeping logic DRY and readable. Tfvars files provide values for input variables from outside the module, enabling environment specific overrides through Terraform’s variable precedence and clean separation of config from code.

  • What is a local module in Terraform?

    A local module in Terraform is a reusable module loaded from your repo via a filesystem path, not the Registry or a remote VCS. Reference it with a path in the source field, for example source = “./modules/network”. It enables reuse without versioning boundaries, so callers pick up changes immediately.

  • What is the difference between data and locals in Terraform?

    Data sources fetch read-only information from providers or remote state during planning or apply, so values can change outside Terraform and require provider access. Locals are pure computed expressions inside a module, no provider calls, deterministic given inputs, used for reuse and clarity, not overrideable by callers.

  • Why use Terraform locals instead of repeating values?

    Using Terraform locals means you define a value or expression once and reference it everywhere it’s needed. If it changes, you update it in one place instead of hunting down every occurrence across your configuration.

  • Can you use functions inside Terraform locals?

    Terraform locals fully support built-in functions, so you can use expressions like merge(), concat(), format(), or toset() directly in a local declaration and reference the result throughout your module.

Terraform Project Structure
Cheat Sheet

Get the Terraform file & project structure

PDF cheat sheet.

terraform files cheat sheet bottom overlay
Share your data and download the cheat sheet