terraform

SKILL.md

Terraform

Common Gotchas

count vs for_each

# count: Index-based, problematic for changes
resource "aws_instance" "web" {
  count = 3  # Removing item 0 shifts all indexes, recreating 1 and 2
}

# for_each: Key-based, stable references
resource "aws_instance" "web" {
  for_each = toset(["a", "b", "c"])  # Removing "a" only affects "a"
}

Use for_each unless you specifically need numeric indexing.

Dependency Cycles

Implicit dependencies via references usually sufficient:

resource "aws_instance" "web" {
  subnet_id = aws_subnet.main.id  # Implicit dependency
}

Use depends_on only for hidden dependencies (IAM policies, etc.):

depends_on = [aws_iam_role_policy_attachment.web]

Sensitive Values

output "password" {
  value     = random_password.db.result
  sensitive = true  # Hides in CLI output, NOT in state file
}

State file still contains plaintext. Always encrypt state at rest.

lifecycle Rules

lifecycle {
  create_before_destroy = true   # New resource before destroying old
  prevent_destroy       = true   # Block terraform destroy
  ignore_changes        = [tags] # Don't track this attribute
  replace_triggered_by  = [null_resource.trigger.id]  # Force replacement
}

State Management

Moving Resources

# Rename in state (no cloud change)
terraform state mv aws_instance.old aws_instance.new

# Move to module
terraform state mv aws_instance.web module.compute.aws_instance.web

# Remove from state only (keep cloud resource)
terraform state rm aws_instance.web

Import

terraform import aws_instance.web i-1234567890abcdef0

After import, run terraform plan and adjust config until no changes.

State Locking

DynamoDB for S3 backend prevents concurrent modifications:

backend "s3" {
  bucket         = "state-bucket"
  key            = "terraform.tfstate"
  dynamodb_table = "terraform-locks"  # Required for locking
}

Module Patterns

Output Dependencies

Child module outputs implicitly depend on all module resources:

# In module
output "instance_id" {
  value = aws_instance.web.id
}

# In root
resource "aws_eip" "web" {
  instance = module.compute.instance_id  # Waits for module completion
}

Module Sources

source = "./modules/vpc"                    # Local
source = "hashicorp/vpc/aws"                # Registry
source = "git::https://example.com/vpc.git" # Git
source = "git::https://example.com/vpc.git?ref=v1.0.0"  # Pinned

Expressions

Conditionals

instance_type = var.env == "prod" ? "t3.large" : "t3.micro"
count         = var.create ? 1 : 0

Splat and for

instance_ids = aws_instance.web[*].id                    # Splat (count)
instance_ids = [for i in aws_instance.web : i.id]       # for (for_each)
instance_map = { for i in aws_instance.web : i.tags.Name => i.id }

Null Coalescing

value = var.custom_value != null ? var.custom_value : "default"
# Or with try():
value = try(var.config.nested.value, "default")
Weekly Installs
3
First Seen
Feb 5, 2026
Installed on
amp3
github-copilot3
codex3
kimi-cli3
gemini-cli3
opencode3