Terraform Lifecycle Rules Explained - Prevent Accidental Destruction
Master Terraform lifecycle meta-arguments. Covers prevent_destroy, create_before_destroy, ignore_changes, and replace_triggered_by with examples.
Terraform
Compare Terraform for_each and count. Learn when to use each with examples covering index stability, conditional creation, and map-based resource management.
Use for_each when resources have meaningful names/keys (stable addressing). Use count for simple "create N copies" or conditional creation (0 or 1). for_each is safer because removing an item doesn't reindex all other resources.
# count creates resources by index
resource "aws_subnet" "private" {
count = 3
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
}
# aws_subnet.private[0], aws_subnet.private[1], aws_subnet.private[2]
# If you remove the middle subnet, indexes shift:
# [0] stays, [1] becomes [0]'s old [2] — Terraform destroys and recreates!# for_each uses keys — no reindexing
resource "aws_subnet" "private" {
for_each = toset(["us-east-1a", "us-east-1b", "us-east-1c"])
cidr_block = cidrsubnet(var.vpc_cidr, 8, index(tolist(toset(["us-east-1a", "us-east-1b", "us-east-1c"])), each.key))
availability_zone = each.key
}
# aws_subnet.private["us-east-1a"], aws_subnet.private["us-east-1b"], ...
# Removing "us-east-1b" only destroys that one subnet — others untouchedvariable "create_bastion" {
type = bool
default = false
}
resource "aws_instance" "bastion" {
count = var.create_bastion ? 1 : 0
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}
# Reference: aws_instance.bastion[0].public_ip (when created)resource "aws_instance" "worker" {
count = var.worker_count
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
tags = { Name = "worker-${count.index}" }
}variable "subnets" {
type = map(object({
cidr = string
az = string
tier = string
}))
default = {
"private-a" = { cidr = "10.0.1.0/24", az = "us-east-1a", tier = "private" }
"private-b" = { cidr = "10.0.2.0/24", az = "us-east-1b", tier = "private" }
"public-a" = { cidr = "10.0.3.0/24", az = "us-east-1a", tier = "public" }
}
}
resource "aws_subnet" "main" {
for_each = var.subnets
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr
availability_zone = each.value.az
tags = { Name = each.key, Tier = each.value.tier }
}resource "aws_iam_user" "team" {
for_each = toset(["alice", "bob", "carol"])
name = each.value
}| Feature | count | for_each |
|---|---|---|
| Address format | resource[0], resource[1] | resource["key"] |
| On item removal | Reindexes — may destroy/recreate | Only removes that item |
| Input type | Number | Set or map |
| Conditional creation | ✅ count = 0 or 1 | ⚠️ Possible but verbose |
| Known at plan time | Must know number | Must know keys |
| Reference syntax | resource[0].id | resource["key"].id |
# Before (count)
variable "az_list" {
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
resource "aws_subnet" "private" {
count = length(var.az_list)
availability_zone = var.az_list[count.index]
}
# After (for_each)
resource "aws_subnet" "private" {
for_each = toset(var.az_list)
availability_zone = each.key
}
# Migrate state:
# terraform state mv 'aws_subnet.private[0]' 'aws_subnet.private["us-east-1a"]'
# terraform state mv 'aws_subnet.private[1]' 'aws_subnet.private["us-east-1b"]'Default to for_each for most use cases — it creates stable, key-based resource addresses that don't shift when items are added or removed. Use count for simple conditional creation (count = var.enabled ? 1 : 0) and fixed-number copies where order doesn't matter.
Master Terraform lifecycle meta-arguments. Covers prevent_destroy, create_before_destroy, ignore_changes, and replace_triggered_by with examples.
Use Terraform dynamic blocks to eliminate repetitive nested blocks. Covers security group rules, IAM policies, and tag generation patterns.
Master conditional expressions in Terraform. Learn ternary operators, conditional resource creation, count and for_each conditionals with examples.
Create and use Terraform modules for reusable infrastructure code. Covers module structure, inputs, outputs, versioning, and the Terraform Registry.