Terraform Data Sources Explained: Read External Information
Learn Terraform data sources to read existing AWS resources, look up AMIs, query remote state, and reference external information in your configurations.
DevOps
Learn Terraform locals to reduce duplication, compute values, and organize complex configurations. Practical examples with naming conventions, tag maps
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.
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"
}
}| Feature | variable | locals |
|---|---|---|
| Set by | User (tfvars, CLI, env) | Terraform author (in code) |
| Purpose | Configurable inputs | Computed/derived values |
| Overridable | ✅ Yes | ❌ No (fixed in code) |
| Validation | ✅ validation block | ❌ No |
| Reference | var.name | local.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.
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.
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}"
}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
}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
}
}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)
}
}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()
}
)
}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"]
}# ❌ 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"
}# ❌ 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"
}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.
Learn Terraform data sources to read existing AWS resources, look up AMIs, query remote state, and reference external information in your configurations.
Learn when to use Terraform depends_on for explicit resource dependencies. Understand implicit vs explicit dependencies, common use cases
Terraform for_each vs count explained with practical examples. Learn when to use each, how to migrate from count to for_each
Understand Terraform variables (inputs) and outputs. Learn types, validation, defaults, sensitive values