Skip to main content

Terraform Locals Explained: Simplify Your Configuration

Key Takeaway

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

Table of Contents

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.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.