Skip to main content

Terraform Local Values

Introduction

When writing Terraform configurations, you often need to transform or combine values before using them in your resources. Terraform's local values feature (commonly referred to as "locals") provides a way to assign a name to an expression, allowing you to use that name multiple times within a module instead of repeating the expression.

Local values are similar to variables, but they can use complex expressions that reference other values in your configuration, making them more powerful for intermediate calculations and value transformations.

What Are Local Values?

Local values are named values that you can reference within your Terraform configuration. Unlike input variables which are meant to be set from outside the module, local values are calculated within the module itself.

Local values help you:

  • Avoid Repetition: Define a complex expression once and reuse it
  • Improve Readability: Name expressions to make their purpose clear
  • Simplify Complex Expressions: Break down complex expressions into smaller, named parts
  • Avoid Recalculation: Compute a value once and reuse it multiple places

Basic Syntax

Local values are defined using the locals block:

hcl
locals {
service_name = "api-gateway"
environment = "production"

# Combining values
name_prefix = "${local.service_name}-${local.environment}"
}

And referenced in your configuration using the local keyword:

hcl
resource "aws_instance" "server" {
tags = {
Name = "${local.name_prefix}-server"
}
}

Local Values vs Variables

Before we dive deeper, let's understand how locals differ from variables:

FeatureLocal ValuesVariables
PurposeInternal calculationsExternal inputs
Declarationlocals blockvariable block
AssignmentDirect in configurationVia command line, files, etc.
Referencelocal.namevar.name
Default valueAlways requiredOptional
ValidationNot directlyBuilt-in validation
Complex expressionsYesLimited (only in defaults)

Common Use Cases

1. Simplifying Tag Management

hcl
locals {
common_tags = {
Project = "MyProject"
Environment = "Dev"
Owner = "DevOps Team"
ManagedBy = "Terraform"
}

service_tags = merge(local.common_tags, {
Service = "API"
})
}

resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"

tags = local.service_tags
}

resource "aws_s3_bucket" "example" {
bucket = "my-tf-test-bucket"

tags = local.common_tags
}

2. Conditional Resource Configuration

hcl
locals {
is_production = var.environment == "prod"
instance_type = local.is_production ? "m5.large" : "t2.micro"
instance_count = local.is_production ? 3 : 1
}

resource "aws_instance" "server" {
count = local.instance_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = local.instance_type
}

3. Data Transformation

hcl
variable "user_information" {
type = list(object({
name = string
email = string
role = string
}))
}

locals {
# Transform list to map with email as key
users_by_email = {
for user in var.user_information :
user.email => user
}

# Filter only admin users
admin_users = [
for user in var.user_information :
user
if user.role == "admin"
]

# Count users by role
users_by_role = {
for role in distinct([for user in var.user_information : user.role]) :
role => length([for user in var.user_information : user if user.role == role])
}
}

4. String Manipulation

hcl
locals {
domain_name = "example.com"
service_names = ["web", "api", "auth"]

# Create a list of fully qualified domain names
fqdns = [for service in local.service_names : "${service}.${local.domain_name}"]

# Join list elements with commas
services_csv = join(", ", local.service_names)
}

5. Working with Maps and Lists

hcl
locals {
environments = {
dev = {
instance_type = "t2.micro"
instance_count = 1
multi_az = false
}
staging = {
instance_type = "t2.medium"
instance_count = 2
multi_az = true
}
prod = {
instance_type = "m5.large"
instance_count = 3
multi_az = true
}
}

# Select configuration based on current environment
env_config = local.environments[var.environment]
}

resource "aws_instance" "app" {
count = local.env_config.instance_count
instance_type = local.env_config.instance_type

# Other configuration...
}

Advanced Techniques

Combining Multiple Local Blocks

You can use multiple locals blocks in your configuration, and they will be merged:

hcl
locals {
project = "myproject"
environment = "production"
}

# Later in the configuration
locals {
resource_prefix = "${local.project}-${local.environment}"
}

Using Functions with Local Values

Terraform's functions can be used to manipulate values:

hcl
locals {
uppercase_project = upper(var.project_name)
project_name_slug = replace(lower(var.project_name), " ", "-")

instance_types = {
small = "t2.micro"
medium = "t2.medium"
large = "m5.large"
}

chosen_instance = lookup(local.instance_types, var.size, local.instance_types.small)
}

Local Values with for Expressions

hcl
variable "users" {
type = list(string)
default = ["john", "mary", "peter"]
}

locals {
# Create user resource names
user_resource_names = [for name in var.users : "user-${name}"]

# Create map of user IDs
user_ids = {
for idx, name in var.users :
name => idx + 100
}
}

Using Local Values with dynamic Blocks

hcl
locals {
ingress_rules = [
{
port = 80
protocol = "tcp"
description = "HTTP"
},
{
port = 443
protocol = "tcp"
description = "HTTPS"
}
]
}

resource "aws_security_group" "example" {
name = "example"

dynamic "ingress" {
for_each = local.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
description = ingress.value.description
cidr_blocks = ["0.0.0.0/0"]
}
}
}

Best Practices

  1. Use Descriptive Names: Choose names that clearly indicate the purpose of the local value.

  2. Group Related Values: Keep related local values together in a single locals block.

  3. Document Complex Expressions: Add comments explaining complex calculations.

  4. Avoid Overuse: Don't create locals for simple values that are only used once.

  5. Use for DRY Configurations: If you're repeating the same expression multiple times, it's a good candidate for a local value.

  6. Keep Locals Focused: Each local should serve a specific, clear purpose.

hcl
locals {
# Good: Descriptive name, clear purpose
standard_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}

# Avoid: Too generic, unclear purpose
stuff = "${var.environment}-data"
}

Complete Example

Let's look at a complete example that combines several techniques:

hcl
variable "project" {
type = string
default = "my-awesome-app"
}

variable "environment" {
type = string
default = "dev"
}

variable "region" {
type = string
default = "us-west-2"
}

variable "instance_count" {
type = number
default = 2
}

# Local values for the module
locals {
# Basic name formatting
name_prefix = "${var.project}-${var.environment}"

# Environment-specific configurations
environment_configs = {
dev = {
instance_type = "t2.micro"
monitoring = false
disk_size = 20
}
staging = {
instance_type = "t2.medium"
monitoring = true
disk_size = 30
}
prod = {
instance_type = "m5.large"
monitoring = true
disk_size = 50
}
}

# Select the appropriate config based on environment
config = local.environment_configs[var.environment]

# Common tags for all resources
common_tags = {
Project = var.project
Environment = var.environment
Region = var.region
ManagedBy = "Terraform"
}

# Generate list of instance names
instance_names = [
for i in range(1, var.instance_count + 1) :
"${local.name_prefix}-instance-${format("%02d", i)}"
]
}

output "configuration" {
value = local.config
}

output "instance_names" {
value = local.instance_names
}

output "tags" {
value = local.common_tags
}

Example output:

Outputs:

configuration = {
"disk_size" = 20
"instance_type" = "t2.micro"
"monitoring" = false
}
instance_names = [
"my-awesome-app-dev-instance-01",
"my-awesome-app-dev-instance-02",
]
tags = {
"Environment" = "dev"
"ManagedBy" = "Terraform"
"Project" = "my-awesome-app"
"Region" = "us-west-2"
}

Summary

Local values are a powerful feature in Terraform that help you:

  • Create intermediate values for reuse throughout your configuration
  • Transform and combine values with complex expressions
  • Make your Terraform code more readable and maintainable
  • Implement DRY (Don't Repeat Yourself) principles in your infrastructure code
  • Simplify complex expressions by breaking them down into named components

By using local values effectively, you can write cleaner, more maintainable Terraform code that's easier to understand and modify.

Additional Resources

Exercises

  1. Create a locals block that generates different resource naming conventions based on an environment variable.

  2. Use local values to implement a tagging strategy for AWS resources that includes required tags like owner, project, and cost center.

  3. Transform a list of user objects into a map where the keys are user IDs and the values are the user objects.

  4. Create a local value that generates different VPC CIDR blocks for different environments, ensuring they don't overlap.

  5. Use locals with conditional expressions to configure different capacity settings for resources based on whether the environment is production or non-production.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)