Quick Answer
Terraform locals (local values) allow you to assign a name to an expression or a complex value, enabling you to reuse that value throughout your configuration without repeating yourself. By defining a locals block, you keep your Infrastructure as Code DRY, readable, and easy to maintain. When building cloud architectures that span multiple AWS regions, Kubernetes clusters, or GitHub repositories, repeating hard-coded strings or complex transformations leads to configuration drift and maintenance burdens. Using local values solves this by centralizing logic in a single location.
Quick Answer
Terraform locals provide a way to assign names to expressions within your HCL configuration. Rather than duplicating calculations, formatting, or conditional lookups across multiple resource blocks, you define a single locals block. For instance, you can combine a project name, environment, and region into a standard naming prefix:
locals {
name_prefix = "${var.project}-${var.environment}-${var.aws_region}"
}
resource "aws_s3_bucket" "example" {
bucket = "${local.name_prefix}-data"
}
When you execute the Terraform CLI workflow, Terraform evaluates these local expressions during the planning phase before generating the execution graph. You verify the resulting values by running terraform plan or by inspecting the expressions interactively using the terraform console command.
Understanding the Concept
In modern infrastructure engineering, keeping code maintainable is paramount. When writing HashiCorp Configuration Language (HCL), you often find yourself applying the same formatting functions, ternary conditions, or string manipulations across numerous cloud resources. Input variables accept values from users or tfvars files, and output values expose data to external consumers or parent modules. Local values, however, fill a different operational niche: they are strictly internal to the module where they are declared.
Syntax and configuration
A locals block is defined using the locals keyword, followed by an opening brace and key-value assignments. Each key acts as an identifier that you can reference anywhere else in the same module using the local.<name> syntax. You can assign strings, numbers, lists, maps, or the results of complex functions. Unlike input variables, you do not specify types or descriptions explicitly inside a standard locals block; Terraform infers the type automatically from the assigned expression.
locals {
environment_name = lower(var.environment)
common_tags = {
Environment = local.environment_name
ManagedBy = "Terraform"
Owner = var.team_owner
}
instance_count = var.environment == "production" ? 3 : 1
}
In this configuration, three separate expressions are calculated once and stored locally. You can then attach local.common_tags to every resource block in your module, ensuring consistent tagging across virtual machines, networking components, and database instances. If your tagging strategy changes tomorrow, you only update the expression in one place rather than editing dozens of individual resource declarations.
CLI workflow
Understanding how local values behave during the standard Terraform CLI lifecycle helps you debug unexpected changes or plan failures. When you initiate a new project or update existing configurations, your workflow typically moves through initialization, planning, and application. Each phase interacts with local values in a specific sequence.
First, you run terraform init to download required providers and modules. Once your provider plugins are active, executing terraform plan triggers the evaluation phase. During plan generation, Terraform parses all HCL files, resolves variable inputs, and computes every expression defined inside your locals blocks. Because locals are evaluated before the dependency graph is fully resolved for resource creation, they can incorporate input variables, data sources, and even outputs from other components, provided there are no circular references.
After reviewing the execution plan output to confirm that local expressions evaluate to the expected strings, numbers, or collections, you execute <a href="/article/terraform-apply-create-and-update-infrastructure-2" class="text-primary font-semibold hover:underline">terraform apply</a>. Terraform uses the pre-computed local values to configure target cloud APIs or container orchestrators without re-evaluating them inconsistently. If you need to test a complex regex replacement or string manipulation inside a local value without running a full plan against live infrastructure, you can launch an interactive shell using terraform console and type local.my_computed_value to see the immediate result.
How It Works
Behind the scenes, local values are integrated directly into Terraform's Directed Acyclic Graph (DAG). When Terraform parses your code, it builds a dependency tree of all objects. Because local values depend only on variables, data sources, or other valid expressions within the module, they are resolved early in the evaluation cycle. Resources that reference local.* automatically inherit a dependency on those evaluations.
This architecture guarantees that local values are always available and fully computed before any resource creation, modification, or destruction request is sent to a provider API. Unlike input variables, which are static constants passed from the outside, locals can perform dynamic transformations. You can merge maps, slice lists, perform conditional logic, and normalize strings. This flexibility makes them indispensable for standardizing naming conventions across distributed systems, whether you are provisioning AWS EC2 instances, deploying workloads to a Kubernetes cluster, or configuring webhooks in a GitHub organization.
Practical Terraform Example
To see how local values solve real-world engineering challenges, consider a scenario where you must provision cloud infrastructure while enforcing strict naming conventions and multi-environment tagging. Hard-coding these strings inside every resource definition increases the likelihood of human error and makes environment promotion difficult.
Example
Here is a complete, production-style configuration example demonstrating how to use local values to construct standardized names and resource tags for a cloud deployment:
variable "environment" {
type = string
description = "Target deployment environment"
default = "staging"
}
variable "project_name" {
type = string
description = "Name of the engineering project"
default = "payment-api"
}
variable "aws_region" {
type = string
description = "Primary AWS region"
default = "us-east-1"
}
locals {
# Sanitize and standardize naming strings
env_slug = lower(var.environment)
proj_slug = lower(var.project_name)
# Construct a reusable naming prefix
name_prefix = "${local.proj_slug}-${local.env_slug}"
# Define centralized tagging metadata
standard_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
CostCenter = "Engineering-Alpha"
}
}
resource "aws_vpc" "main" {
cidr_block = "10.100.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(
local.standard_tags,
{
Name = "${local.name_prefix}-vpc"
}
)
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.100.1.0/24"
availability_zone = "${var.aws_region}a"
tags = merge(
local.standard_tags,
{
Name = "${local.name_prefix}-public-subnet"
}
)
}
When you run terraform plan against this configuration, Terraform evaluates the locals block first. It converts the environment and project strings to lowercase, concatenates them into local.name_prefix, and merges local.standard_tags with specific resource name tags. The expected plan output displays the resolved tags and VPC configurations clearly, ensuring no unexpected strings are generated before you execute terraform apply.
Verification
Verifying that your local expressions evaluate correctly before touching production infrastructure is a critical step in any robust CI/CD pipeline. Blindly applying changes without inspecting the execution plan can lead to accidental resource recreation or naming collisions in cloud environments.
The primary method for verification is executing terraform plan. Review the output terminal to ensure that computed resource attributes match your expectations. For deeper inspection of complex expressions, use the interactive command line console by typing terraform console. Once the prompt appears, you can type any local reference, such as local.standard_tags or local.name_prefix, and press Enter to view the exact evaluated data structure.
In automated CI/CD pipelines running on platforms like GitHub Actions, you should always enforce a mandatory plan review gate. Configure your pipeline to run terraform plan -out=tfplan and display the summarized changes in a pull request comment. This allows team members to inspect the evaluated local values and resource modifications before any deployment script runs terraform apply.
Common Mistakes
Even experienced engineers encounter pitfalls when working with local values. Recognizing these common errors helps you write cleaner, more resilient HCL code.
One frequent mistake is creating circular dependencies. A local value cannot reference itself, nor can it reference a resource attribute that in turn depends on that same local value in a way that forms a closed loop. Always ensure that data flows in a single direction: from variables and data sources into locals, and from locals into resources.
Another trap is overcomplicating expressions. While you can nest ternary operators, string splits, and JSON parsing functions inside a single local definition, doing so makes your code difficult for other engineers to read and debug. If an expression requires multiple lines of complex logic, break it down into several smaller, well-named local values.
Finally, never hard-code secrets, API tokens, or private keys inside a locals block. Because local values are stored in plaintext within state files and plan outputs, embedding sensitive credentials here compromises your security posture. Always retrieve sensitive data securely using dedicated secret management tools or secure provider data sources.
Best Practices
Adopting established conventions for local values ensures your infrastructure code remains scalable and easy for teams to audit.
Keep your locals blocks organized at the top of your configuration files, typically right beneath input variable declarations and above resource definitions. Use clear, descriptive names that explain what the expression represents rather than how it was calculated. For example, name an expression subnet_cidr_list rather than calc_vals.
Limit the scope of locals to the module where they are needed. If you find yourself duplicating the exact same complex local computation across multiple modules, consider encapsulating that logic into a dedicated shared child module. Maintain clean state boundaries by ensuring that local values do not attempt to bypass Terraform's resource dependency tracking.
Troubleshooting
When a local expression fails to evaluate or produces unexpected output, systematic troubleshooting helps you isolate the root cause quickly.
If Terraform returns an error stating that a local value cannot be found, verify that you are referencing it with the correct prefix (local.<name>) and that the spelling matches the key defined in your locals block. Check that the file containing the locals block is loaded within the current module scope.
When dealing with complex data transformations, such as merging nested maps or filtering lists, use terraform console to test individual functions interactively. For instance, if a zipmap or for expression produces unexpected null values or type mismatches, run that exact snippet in the console to inspect the intermediate output. If provider-specific attributes cause evaluation errors, verify your provider version constraints in the required_providers block, as breaking changes in provider schemas can alter how functions interpret data structures.
📌 Recommended Next Guides & References
<li>
<a href="/article/docker-and-kubernetes-how-they-work-together-2" class="text-primary hover:underline font-semibold flex items-center gap-2">
<span>→</span> <span>Docker and Kubernetes: How They Work Together</span>
</a>
</li>
<li>
<a href="/article/kubernetes-ingress-explained" class="text-primary hover:underline font-semibold flex items-center gap-2">
<span>→</span> <span>Kubernetes Ingress Explained: Routing, Controllers, and TLS</span>
</a>
</li>
<li>
<a href="/article/kubernetes-ingress-controller-explained" class="text-primary hover:underline font-semibold flex items-center gap-2">
<span>→</span> <span>Kubernetes Ingress Controller Explained: Architecture, Routing, and Implementation</span>
</a>
</li>
