In today’s blog post, we will learn terraform local variables better known as local values. While input variables are the standard way to define variables in terraform, you can use local variable to temporarily perform some data manipulation and store the outcome.
This detailed guide will walk you through everything you need to know about Terraform Local Values. We will explore their purpose, common use cases, how they differ from input/output variables, and provide practical examples that will help you understand and utilize local variables in your terraform code.
What are Terraform Local Values?
Terraform Local Values (often informally called “local variables”) are named expressions that you define and use within a single Terraform module. Think of them as temporary, computed values or constants that help avoid repeating complex expressions or values multiple times throughout your configuration.
They act like internal variables, simplifying complex logic and making your configuration more readable and DRY (Don’t Repeat Yourself).
Why Use Local Values?
- Reduce Repetition (DRY Principle): Avoid writing the same complex expression or hardcoded value repeatedly.
- Improve Readability: Assign meaningful names to complex expressions, making your code easier to understand.
- Simplify Logic: Break down complex calculations or data transformations into smaller, manageable pieces.
- Enhance Maintainability: Changes to a common value or expression only need to be made in one place.
- Enable Dynamic Configurations: Compute values based on inputs or other resources, driving dynamic resource creation or configuration.
How to Define Local Values
You define local values within a locals
block. This block can appear in any .tf
file within your root modules or sub module, though it is common practice to put them in a dedicated locals.tf
file for organization.
# locals.tf
locals {
# Example 1: Simple string concatenation
resource_name_prefix = "my-app-${var.environment}"
# Example 2: List transformation
private_subnet_cidrs = [for i, vpc_cidr in var.vpc_cidr_blocks : cidrsubnet(vpc_cidr, 8, i + 1)]
# Example 3: Map of tags (often used for consistent tagging)
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}
# Example 4: Conditional logic
instance_type_based_on_env = var.environment == "prod" ? "m5.large" : "t3.medium"
}
Explanation:
locals { ... }
: This block defines all your local values.resource_name_prefix = "my-app-${var.environment}"
: Each line defines a local value. The name (e.g.,resource_name_prefix
) is on the left, and the expression that computes its value is on the right.- Local values can reference input variables (
var.environment
), other local values, or even attributes of resources.
Referencing Local Values
Once defined, you reference local values using the local.
prefix, similar to how you use var.
for input variables.
# main.tf
resource "aws_instance" "web_server" {
ami = data.aws_ami.ubuntu.id # Assuming you have an AMI data source
instance_type = local.instance_type_based_on_env
count = var.instance_count # Input variable
tags = local.common_tags # Using a map local value
user_data = <<-EOF
#!/bin/bash
echo "Hello from ${local.resource_name_prefix}!" > /var/www/html/index.html
EOF
}
resource "aws_s3_bucket" "app_data" {
bucket = "${local.resource_name_prefix}-data-bucket"
acl = "private"
tags = local.common_tags
}
Terraform Locals vs. Variables vs. Outputs
Let us explore the difference between terraform Local Values vs. Input Variables vs. Output Values
Feature | Input Variables (variable ) | Local Values (locals ) | Output Values (output ) |
---|---|---|---|
Purpose | Customize a module from the outside. User input. | Compute and store intermediate values inside a module. | Expose data from a module to the outside. |
Scope | Module-wide. Values provided at runtime. | Module-wide. Only accessible within the module they’re defined. | Module-wide. Exposed after terraform apply . |
Source of Value | User input (CLI, .tfvars, environment variables, default). | Expressions based on inputs, other locals, resource attributes. | Resource attributes, local values, input variables. |
Mutability | Immutable once set for a terraform run. | Computed once per terraform run based on their expression. | Immutable once resources are provisioned. |
Visibility | Visible to the user running Terraform. | Internal to the module. Not directly exposed outside. | Visible to the user, or other configurations/modules. |
Prefix | var. | local. | output. (when referenced from another module) |
When to choose which:
- Use Input Variables when a value needs to be passed into your module from an external source (e.g., environment name, instance count, specific configuration parameters).
- Use Local Values when you need to perform calculations, transformations, or consolidate common expressions within your module for internal use (e.g., creating resource names, complex conditional logic, derived values).
- Use Output Values when you want to output a specific piece of information from your module to the outside world, so other Terraform configurations or external tools can consume it (e.g., VPC ID, load balancer endpoint, public IP).
Common Use Cases for Local Values
Consistent Naming Conventions
This is perhaps the most frequent use case. By defining a local value for a common name prefix, you ensure consistency across all your resources.
# locals.tf
locals {
project_name = "ecommerce"
environment_suffix = var.environment == "prod" ? "" : "-${var.environment}" # Add suffix for non-prod
base_name = "${local.project_name}${local.environment_suffix}"
}
# main.tf
resource "aws_s3_bucket" "app_logs" {
bucket = "${local.base_name}-logs"
# ...
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "${local.base_name}-vpc"
}
# ...
}
Standardized Tagging
Applying a consistent set of tags across all resources is a best practice for cost allocation, organization, and automation. Locals make this effortless.
# locals.tf
locals {
common_tags = {
ManagedBy = "Terraform"
Environment = var.environment
Owner = var.owner_email
DateCreated = formatdate("YYYY-MM-DD", timestamp())
}
}
# main.tf
resource "aws_ec2_instance" "web" {
# ...
tags = local.common_tags
}
resource "aws_rds_instance" "db" {
# ...
tags = merge(local.common_tags, {
DatabaseType = "PostgreSQL"
}) # You can merge locals with additional tags
}
Data Transformations and Mappings
When you need to transform input data or create mappings, locals are ideal.
# variables.tf
variable "instance_sizes" {
type = map(string)
default = {
"dev" = "t3.small"
"staging" = "t3.medium"
"prod" = "m5.large"
}
}
# locals.tf
locals {
# Get the instance type for the current environment
current_instance_type = lookup(var.instance_sizes, var.environment, "t3.nano") # default to t3.nano if not found
# Create a map for conditional resource creation using `for` loop
instance_config = {
for env, instance_type in var.instance_sizes : env => {
type = instance_type
disk_size = env == "prod" ? 100 : 50
public_ip = env == "dev" ? true : false
}
}
}
# main.tf
resource "aws_instance" "env_instance" {
for_each = local.instance_config
ami = data.aws_ami.ubuntu.id
instance_type = each.value.type
associate_public_ip_address = each.value.public_ip
tags = merge(local.common_tags, {
Name = "${local.base_name}-${each.key}-instance"
})
}
Simplifying Conditional Logic
Complex condition ? true_val : false_val
expressions can be encapsulated in locals for better readability.
# locals.tf
locals {
# Determine if a public IP should be associated based on environment
should_associate_public_ip = var.environment == "dev" || var.environment == "staging" ? true : false
# Choose the subnet ID based on a boolean flag
subnet_id = var.use_private_subnet ? var.private_subnet_id : var.public_subnet_id
}
# main.tf
resource "aws_instance" "app" {
# ...
associate_public_ip_address = local.should_associate_public_ip
subnet_id = local.subnet_id
}
Derived Values from Resource Attributes
Locals can compute values based on attributes of resources that have already been defined or will be provisioned.
# locals.tf
locals {
# Concatenate IDs of all created subnets (requires a resource defined elsewhere)
all_subnet_ids_string = join(",", aws_subnet.app_subnets.*.id)
}
# main.tf
output "combined_subnet_ids" {
description = "A comma-separated string of all application subnet IDs."
value = local.all_subnet_ids_string
}
Best Practices for Local Values
- Organize in
locals.tf
: While not strictly required, placing all yourlocals
blocks in a dedicatedlocals.tf
file (or grouping them logically) enhances readability. - Descriptive Naming: Use clear, self-explanatory names for your local values (e.g.,
resource_name_prefix
,common_tags
,db_endpoint
). - Avoid Circular Dependencies: A local value cannot reference another local value that directly or indirectly references itself. Terraform will throw an error (
Circular dependency detected!
). - Do not Overuse: Locals are for internal computation and readability. Do not use them to replace input variables if a value truly needs to be passed into the module. Similarly, do not use them if the value’s primary purpose is to be exposed to other modules; that is what outputs are for.
- Keep Expressions Focused: If a local value’s expression becomes excessively complex, consider breaking it down into multiple, simpler local values.
- Test Your Locals: Before applying,
terraform plan
will evaluate your local values, allowing you to catch errors or unexpected results early.
Conclusion
Terraform Local Values is an important feature for writing clean, efficient, and maintainable Infrastructure as Code. By wisely applying them for consistent naming, standardized tagging, data transformations, and simplifying complex logic, you can significantly enhance your Terraform configurations. Use local values today and see their power!
Author

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins. In my free time, I write blogs on ckdbtech.com