TerraformPilot

DevOps

Terraform Locals Explained: Simplify Your Configuration

Learn Terraform locals to reduce duplication, compute values, and organize complex configurations. Practical examples with naming conventions, tag maps

LLuca Berton1 min read

Locals are computed values within your Terraform configuration — think of them as named constants or intermediate variables. They reduce duplication, make complex expressions readable, and keep your code DRY.

Basic Syntax

#
locals {
  project     = "myapp"
  environment = terraform.workspace
  region      = "us-east-1"
 
  # Computed from other locals
  name_prefix = "${local.project}-${local.environment}"
}
 
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
 
  tags = {
    Name = "${local.name_prefix}-vpc"  # "myapp-prod-vpc"
  }
}

Locals vs Variables

#
Featurevariablelocals
Set byUser (tfvars, CLI, env)Terraform author (in code)
PurposeConfigurable inputsComputed/derived values
Overridable✅ Yes❌ No (fixed in code)
Validationvalidation block❌ No
Referencevar.namelocal.name
# Variable: user provides the value
variable "environment" {
  type    = string
  default = "dev"
}
 
# Local: you compute the value
locals {
  is_production = var.environment == "prod"
  instance_type = local.is_production ? "t3.xlarge" : "t3.micro"
}

Rule: If the user should be able to change it → variable. If it's derived from other values → locals.

Common Patterns

#

1. Standard Tags

#
locals {
  common_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
    Team        = var.team
    CostCenter  = var.cost_center
  }
}
 
resource "aws_instance" "web" {
  ami           = data.aws_ami.al2023.id
  instance_type = "t3.micro"
 
  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-web"
    Role = "web-server"
  })
}
 
resource "aws_s3_bucket" "data" {
  bucket = "${local.name_prefix}-data"
  tags   = local.common_tags
}
 
resource "aws_rds_instance" "main" {
  identifier = "${local.name_prefix}-db"
  # ...
  tags = merge(local.common_tags, {
    Name     = "${local.name_prefix}-db"
    Backup   = "daily"
    Snapshot = "true"
  })
}

Every resource gets consistent tags without repeating the map.

2. Naming Conventions

#
locals {
  name_prefix = "${var.project}-${var.environment}"
 
  # Resource-specific names
  vpc_name     = "${local.name_prefix}-vpc"
  cluster_name = "${local.name_prefix}-ecs"
  db_name      = "${local.name_prefix}-db"
  bucket_name  = "${local.name_prefix}-data-${data.aws_caller_identity.current.account_id}"
}

3. Conditional Logic

#
locals {
  is_production = var.environment == "prod"
 
  # Different configs per environment
  instance_type    = local.is_production ? "t3.xlarge"  : "t3.micro"
  multi_az         = local.is_production ? true         : false
  min_capacity     = local.is_production ? 3            : 1
  max_capacity     = local.is_production ? 10           : 2
  enable_monitoring = local.is_production
  backup_retention = local.is_production ? 30           : 7
}
 
resource "aws_db_instance" "main" {
  instance_class       = local.instance_type
  multi_az             = local.multi_az
  backup_retention_period = local.backup_retention
  monitoring_interval  = local.enable_monitoring ? 60 : 0
}

4. Data Transformation

#
variable "subnet_config" {
  default = {
    public = {
      cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
      public = true
    }
    private = {
      cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
      public = false
    }
  }
}
 
locals {
  azs = data.aws_availability_zones.available.names
 
  # Flatten into a map for for_each
  all_subnets = merge([
    for tier, config in var.subnet_config : {
      for i, cidr in config.cidrs :
      "${tier}-${local.azs[i]}" => {
        cidr   = cidr
        az     = local.azs[i]
        tier   = tier
        public = config.public
      }
    }
  ]...)
}
 
resource "aws_subnet" "all" {
  for_each = local.all_subnets
 
  vpc_id                  = aws_vpc.main.id
  cidr_block              = each.value.cidr
  availability_zone       = each.value.az
  map_public_ip_on_launch = each.value.public
 
  tags = {
    Name = "${local.name_prefix}-${each.key}"
    Tier = each.value.tier
  }
}

5. Computed CIDR Blocks

#
locals {
  vpc_cidr = "10.0.0.0/16"
 
  public_subnets = {
    for i, az in local.azs :
    az => cidrsubnet(local.vpc_cidr, 8, i)
  }
 
  private_subnets = {
    for i, az in local.azs :
    az => cidrsubnet(local.vpc_cidr, 8, i + 100)
  }
 
  database_subnets = {
    for i, az in local.azs :
    az => cidrsubnet(local.vpc_cidr, 8, i + 200)
  }
}

6. Merging Configuration

#
variable "additional_tags" {
  type    = map(string)
  default = {}
}
 
locals {
  # Base tags + user-provided tags + auto-generated tags
  all_tags = merge(
    local.common_tags,
    var.additional_tags,
    {
      LastUpdated = timestamp()
    }
  )
}

Multiple locals Blocks

#

You can have multiple locals blocks — Terraform merges them:

# locals-naming.tf
locals {
  name_prefix = "${var.project}-${var.environment}"
}
 
# locals-tags.tf
locals {
  common_tags = {
    Project     = var.project
    Environment = var.environment
  }
}
 
# locals-network.tf
locals {
  vpc_cidr = "10.0.0.0/16"
  azs      = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

Anti-Patterns

#

Don't Over-Abstract

#
# ❌ Too many levels of indirection
locals {
  a = var.x
  b = local.a
  c = local.b
  d = local.c  # What is this even?
}
 
# ✅ Direct and readable
locals {
  instance_type = var.environment == "prod" ? "t3.xlarge" : "t3.micro"
}

Don't Use Locals for Simple Values

#
# ❌ Unnecessary — just use the variable directly
locals {
  region = var.region
}
 
# ✅ Use local only when you're computing something
locals {
  region_short = replace(var.region, "-", "")  # "useast1"
}

Hands-On Courses

#

Conclusion

#

Locals keep your Terraform code DRY and readable. Use them for standard tags, naming conventions, conditional logic, and data transformation. Don't use them as variable aliases — only create a local when you're computing or deriving a value. The most common pattern is common_tags + name_prefix — these two locals alone eliminate hundreds of lines of duplication in a typical project.

#Terraform#DevOps#IaC#HCL

Share this article