Terraform configurations require a way to express and store values within the context of a code block. That functionality is delivered through Terraform local values. 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 values that can be used to request or public values, with the other two being input variables and output values. The distinction matters because locals are internal to a module, input variables are the interface from outside, and outputs are the interface outward.
Local values are named, module-scoped values that let you assign an expression once and reuse it throughout your configuration, reducing duplication and making code easier to read and maintain. If you have 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.
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. Locals are pure computed expressions inside a module, no provider calls, deterministic given inputs, used for reuse and clarity, not overrideable by callers.
What Terraform Locals Are and How They Differ From Input Variables
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. The scope difference is foundational. 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.
The impact for practitioners is immediate. Repeated expressions create drift risk. When a naming convention, tag set, or computed prefix appears in ten resources, a change requires ten edits. A local centralizes that edit to one place. The readability gain compounds in larger projects where team members must understand intent quickly.
Defining Local Values With the locals Block
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 type flexibility allows locals to hold simple scalars and complex structures.
The reference syntax uses interpolation or direct reference. An example of how you can define a local in Terraform is:
locals {
region = "us-west-2"
ami = data.aws_ami.ubuntu.id
}
You can reference these locals later in your Terraform code by using the ${local.region} and ${local.ami} syntax.
Locals are often stored in a file called locals.tf. There is no strict requirement for this, however it could be considered a best practice for organizing your Terraform code, especially in larger projects to help keep your code readable, and maintainable.
Storing locals in a dedicated file creates a clear separation of concern. The file becomes a single source for derived constants. When a module grows, the reader can open locals.tf to understand naming conventions, computed prefixes, and shared expressions without scanning resource blocks.
Module Scope, Override Rules, and Naming Conventions
Terraform locals are named, module-scoped values that let you assign an expression once and reuse it throughout your configuration. The module scope means a local is visible only within the module where it is defined. Callers cannot set it. This is in contrast to input variables, which form the public interface of a module and can be set by callers via tfvars, CLI, or other mechanisms.
The non-overrideable nature makes locals ideal for intermediate values derived from other resources, data sources, or expressions. Because they cannot be overridden from outside the module, they protect internal logic from external mutation.
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.
The naming convention locals block versus local.
Data Types, Expressions, and Built-In Functions
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.
An example of a local declaration in a Terraform script is:
locals {
bucket_name = "${var.text1}-${var.text2}"
}
This combines two input variables into a computed name that can be reused across multiple resources without repeating the interpolation.
The ability to use functions inside locals means complex transformations can be encapsulated. A map of tags can be built with merge(), a list of names can be built with concat(), and formatting can be standardized with format(). Because the result is stored once, downstream resources reference a stable name.
The impact is reduced error surface. Function logic is tested once. When a function changes, all consumers see the change automatically. This also improves plan readability because the plan shows the local name rather than a repeated expression.
Practical Patterns With AWS Resources and Data Lookups
Locals in Terraform allow you to define values that are computed from expressions. You can use locals to simplify your infrastructure code and make it more readable.
A common pattern is to define a region local and an ami local that references the ID of an Ubuntu AMI from a data resource. The local acts as an indirection layer between the data lookup and resource definitions.
Data resources in Terraform allow you to retrieve data from an external data source, such as an AWS EC2 instance, an AWS S3 bucket, or a DNS record. You can use data resources to obtain information that is required to provision infrastructure resources. Using a data resource, over using a fixed value or even an input, gives you an advantage when wanting to keep your infrastructure up-to-date.
When a local references a data resource, the local becomes a computed constant that is deterministic given inputs. The data source may change outside Terraform, and the local will reflect that change on the next plan. This separation keeps resource definitions clean.
The table below summarizes the relationship between locals, data sources, and variables.
| Construct | Scope | Overrideable by caller | Source of value | Typical use |
|---|---|---|---|---|
| locals | module | no | expression inside module | reuse and DRY |
| input variables | module interface | yes | tfvars, CLI, etc | external configuration |
| data sources | provider | no | external provider read | read-only information |
| output values | module interface | n/a | computed module results | expose to caller |
Locals are pure computed expressions inside a module, no provider calls, deterministic given inputs, used for reuse and clarity, not overrideable by callers. 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 Versus Tfvars, Variables, Outputs, and Local Modules
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.
The distinction between local modules and locals is important. Local modules refer to module source paths. Locals refer to values inside a module. Both support local development workflows.
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. The concepts of locals, variables, and data sources carry across.
Practicing Terraform Locally Without Cloud Providers
Practicing Terraform locally offers several advantages, especially for those who are new to infrastructure as code or want to experiment without incurring cloud costs.
- Cost-Effective Learning: You can experiment and learn Terraform without the need for cloud resources, which can be costly.
- Rapid Iteration: Local environments allow for quick testing and iteration, speeding up the learning process.
- Isolated Environment: A local setup provides a sandboxed environment where you can make mistakes without affecting production systems.
- No Cloud Dependencies: You can practice Terraform without needing access to cloud providers, making it accessible to everyone.
- Realistic Environment: Using tools like kind allows you to simulate a real Kubernetes cluster locally.
Before diving into Terraform configurations, let's start by setting up a local Kubernetes cluster using kind. This will provide the foundation for our Terraform practice environment.
Kind is a lightweight tool for running local Kubernetes clusters using Docker containers as nodes.
Local practice removes the risk of unintended changes to real infrastructure. Learners can experiment with locals, variables, and data sources against a local kind cluster, iterating rapidly without cloud costs. The isolated environment ensures mistakes stay contained. The realistic environment provided by kind allows Terraform providers for Kubernetes to behave similarly to production.
Common Errors and Readability Impact
Locals are declared with the locals block plural but referenced with local.
Another frequent mistake is treating locals as inputs. Because locals cannot be overridden from outside the module, attempting to pass a value into a local via a variable assignment will not work. The correct pattern is to accept input via a variable and derive a local from it.
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.
The readability impact is cumulative. A module that centralizes naming, tag merging, and computed prefixes into locals is easier to review in pull requests. Reviewers see one definition and many references rather than repeated expressions. This reduces cognitive load and enforces consistency.
Conclusion
Terraform locals provide a module-scoped mechanism to define computed values once and reuse them throughout a configuration. They reduce duplication, increase readability, and protect internal logic from external override. Locals support all valid Terraform data types and built-in functions, enabling complex transformations to be encapsulated and referenced via local.
Organizing locals in a file such as locals.tf supports maintainability in larger projects. Pairing locals with local development practices using tools like kind allows teams to learn and experiment without cloud costs, rapid iteration, and isolated environments. The combination of module-scoped reuse through locals and local-first practice creates a safer learning path and a more maintainable codebase.