TFLAB07-Locals: DRY Configuration with Computed Values

Locals: DRY Configuration with Computed Values

🔧 Terraform Core ⭐ Beginner locals local.* merge() DRY Principle

Scenario

You have a configuration where the same tag map, naming prefix, and computed values are repeated across a dozen resources. Every time you update a tag, you have to change it in 12 places. This violates the DRY (Don't Repeat Yourself) principle and makes maintenance painful.

You need to extract repeated expressions into locals — computed values that are defined once and referenced everywhere. Unlike variables, locals can contain expressions that reference other resources, variables, and even other locals.

Your Objectives
  • Create a locals {} block with a naming prefix that combines project name and environment.
  • Create a common tags map in locals that all resources share.
  • Use merge() to combine common tags with resource-specific tags.
  • Create a local that computes a value from a variable (e.g., uppercase environment).
  • Reference locals in at least two resources using local.*.

Additional Context

locals vs variables: Variables are inputs from outside the configuration (the caller sets them). Locals are internal computed values — derived from variables, resources, or expressions. You never set a local from outside; it's always computed inside the config.

Think of locals as "private constants with formulas." They keep your code DRY and make complex expressions readable by giving them meaningful names. Reference them with local.name (singular, not plural).

variables.tf

variable "project"     { type = string; default = "webapp" }
variable "environment" { type = string; default = "dev" }
variable "aws_region"  { type = string; default = "us-east-1" }

main.tf

# main.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

provider "aws" { region = var.aws_region }

# ─── LOCALS: Define once, use everywhere ───
locals {
  # Computed naming prefix — used in every resource name
  prefix = "${var.project}-${var.environment}"

  # Uppercase environment — computed from a variable
  env_upper = upper(var.environment)

  # Common tags — applied to every resource via merge()
  common_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

# Resource 1 — uses local.prefix and local.common_tags
resource "aws_s3_bucket" "app_data" {
  bucket = "${local.prefix}-data"

  # merge() takes common tags and adds resource-specific ones
  tags = merge(local.common_tags, {
    Name    = "${local.prefix}-data"
    Purpose = "application-data"
  })
}

# Resource 2 — same locals, different resource
resource "aws_s3_bucket" "app_logs" {
  bucket = "${local.prefix}-logs"

  tags = merge(local.common_tags, {
    Name    = "${local.prefix}-logs"
    Purpose = "application-logs"
  })
}

outputs.tf

output "prefix"          { value = local.prefix }
output "env_upper"       { value = local.env_upper }
output "common_tags"     { value = local.common_tags }
output "data_bucket"     { value = aws_s3_bucket.app_data.id }
output "logs_bucket"     { value = aws_s3_bucket.app_logs.id }

Workflow Commands

terraform init && terraform apply

# Both buckets share the same prefix and common tags
terraform output prefix       # "webapp-dev"
terraform output env_upper    # "DEV"

terraform destroy
✓ Expected Output After Apply
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

common_tags  = tomap({
  "Environment" = "dev"
  "ManagedBy"   = "terraform"
  "Project"     = "webapp"
})
data_bucket  = "webapp-dev-data"
env_upper    = "DEV"
logs_bucket  = "webapp-dev-logs"
prefix       = "webapp-dev"