Infra iac terraform
Skill agents-inc/skills/dist/plugins/infra-iac-terraform/skills/infra-iac-terraform
Infrastructure as Code with HashiCorp TerraformFrom its SKILL.md
npx -y skills add agents-inc/skills --skill infra-iac-terraformAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 20 stars20 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
17.2 KB, ~4.0k tokens by cl100k_base, as published. Nobody here has run it
Terraform Patterns
Quick Guide: Declarative infrastructure using HCL. Pin provider versions in
required_providersand commit.terraform.lock.hcl. Use remote backends with state locking for team collaboration. Preferfor_eachovercountfor non-identical resources. Usemovedblocks for refactoring,importblocks for adopting existing infrastructure. Validate inputs withvalidationblocks and infrastructure withprecondition/postcondition. Keep modules flat, composable, and single-purpose. Runterraform fmtandterraform validatebefore every commit.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md
(You MUST pin provider versions with constraints in required_providers and commit .terraform.lock.hcl to version control)
(You MUST use a remote backend with state locking for any shared or production infrastructure)
(You MUST use for_each with a map/set for non-identical resources -- count causes index-shift destruction on removal)
(You MUST never store secrets in .tf files, .tfvars, or state -- use environment variables (TF_VAR_*) or your secrets manager)
(You MUST run terraform plan and review the diff before every terraform apply -- never apply blindly)
</critical_requirements>
Detailed Resources:
- examples/core.md - Resource definitions, variables, outputs, locals, data sources, provider configuration
- examples/modules.md - Module structure, composition, versioning, registry publishing
- examples/state.md - Remote backends, state locking, moved/import/removed blocks, workspaces
- examples/patterns.md - for_each, dynamic blocks, lifecycle, conditions, validations
- reference.md - Decision frameworks, CLI cheat sheet, file naming conventions
Auto-detection: Terraform, OpenTofu, HCL, .tf files, terraform init, terraform plan, terraform apply, terraform fmt, terraform validate, required_providers, terraform block, resource block, data source, module block, variable block, output block, locals, backend configuration, remote state, state locking, moved block, import block, for_each, count, dynamic block, lifecycle, precondition, postcondition, .terraform.lock.hcl, tfvars, provider configuration
When to use:
- Writing or reviewing Terraform/OpenTofu configuration files (
.tf) - Defining cloud resources, data sources, modules, variables, and outputs
- Managing state backends, locking, and multi-environment deployments
- Refactoring infrastructure with
moved,import, andremovedblocks - Structuring reusable modules for team or registry consumption
When NOT to use:
- Application code deployment logic (that belongs in CI/CD pipelines)
- Container orchestration configuration (Kubernetes manifests, Helm charts)
- One-off scripting tasks better handled by shell scripts or CLI tools
Key patterns covered:
- Provider pinning, lock files, and version constraints
- Resource definitions with meta-arguments (
for_each,count,depends_on,lifecycle) - Variable validation, locals for derived values, output descriptions
- Remote backend configuration with state locking
- Module structure (flat composition, single-purpose modules)
- Refactoring with
moved,import, andremovedblocks - Custom conditions (
precondition,postcondition,checkblocks) - Dynamic blocks for repeated nested configuration
- Environment management (directory-based vs workspaces)
<philosophy>
Philosophy
Terraform is a declarative infrastructure-as-code tool. You describe the desired end-state; Terraform determines the steps to reach it. The HCL configuration language is designed to be human-readable and machine-parseable.
Core principles:
- Declarative, not imperative -- describe what you want, not how to get there
- State is the source of truth -- Terraform tracks what it manages via state; protect it accordingly
- Modules are the unit of reuse -- keep them flat, composable, and single-purpose
- Pin everything -- provider versions, Terraform version, module versions; reproducibility is non-negotiable
- Plan before apply -- always review the diff; never apply blindly in production
OpenTofu compatibility: OpenTofu is an open-source fork (MPL 2.0) that is syntax-compatible with Terraform 1.5.x. The patterns in this skill apply to both tools. OpenTofu uses .tofu file extensions for OpenTofu-only features and adds native state encryption.
<patterns>
Core Patterns
Pattern 1: Provider and Version Pinning
Pin Terraform version and all provider versions. Commit .terraform.lock.hcl to version control.
# terraform.tf
terraform {
required_version = ">= 1.9.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Allows 5.x, blocks 6.0
}
}
}
Why this matters: Without version constraints, terraform init on different machines downloads different provider versions, causing inconsistent plans and mysterious drift. The lock file pins exact versions and cryptographic hashes.
Version constraint syntax: = 1.0.0 (exact), >= 1.0.0 (minimum), ~> 1.0 (allows 1.x, blocks 2.0), >= 1.0, < 2.0 (range).
See examples/core.md for full provider configuration with aliases and default tags.
Pattern 2: Resource Definitions and Meta-Arguments
Resources follow a standard argument ordering: meta-arguments first, resource arguments next, nested blocks after, lifecycle last.
resource "aws_instance" "web" {
count = var.instance_count # Meta-argument first
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = "web-${count.index}"
}
lifecycle { # Lifecycle block last
create_before_destroy = true
}
}
Key meta-arguments: count, for_each, depends_on, provider, lifecycle. Place meta-arguments at the top, separated from resource arguments by a blank line.
See examples/core.md for argument ordering and naming conventions.
Pattern 3: for_each over count
Use for_each with a map or set for non-identical resources. count uses numeric indices -- removing an item from the middle shifts all subsequent indices, causing unnecessary destruction and recreation.
# for_each with a map -- stable keys, safe removal
resource "aws_iam_user" "team" {
for_each = toset(var.team_members) # ["alice", "bob", "carol"]
name = each.value
}
# Removing "bob" only destroys bob's user -- alice and carol are untouched
# count -- index-based, dangerous on removal
resource "aws_iam_user" "team" {
count = length(var.team_members)
name = var.team_members[count.index]
}
# Removing "bob" (index 1) shifts carol from index 2 to 1 -- carol gets destroyed and recreated
When count is acceptable: Identical resources where the only difference is the count (e.g., count = var.enable_feature ? 1 : 0 for conditional creation).
See examples/patterns.md for for_each with maps, sets, and conditional patterns.
Pattern 4: Variables with Validation
Every variable needs type, description, and validation where constraints exist. Use named locals for derived values.
variable "environment" {
type = string
description = "Deployment environment (dev, staging, production)"
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
variable "instance_type" {
type = string
description = "EC2 instance type for the application server"
default = "t3.micro"
}
Key rules: Always set type and description. Use validation blocks to catch invalid input at plan time. Never use default for secrets -- force the caller to provide them.
See examples/core.md for complex variable types, sensitive variables, and output definitions.
Pattern 5: Remote Backend with State Locking
Never use local state for shared infrastructure. Remote backends provide locking, versioning, and team collaboration.
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks" # State locking
}
}
Critical: Backend configuration cannot use variables or locals -- values must be literal or passed via -backend-config flags during terraform init. Use partial configuration for dynamic values.
State file hierarchy: Organize state keys by environment and layer (e.g., prod/network/, prod/compute/, staging/network/) to minimize blast radius.
See examples/state.md for backend configuration, partial config, and state organization.
Pattern 6: Module Structure and Composition
Modules are the unit of reuse. Keep modules flat, single-purpose, and composable.
modules/
vpc/
main.tf # Resources
variables.tf # Inputs
outputs.tf # Outputs
README.md # Usage docs (makes it public-facing)
compute/
main.tf
variables.tf
outputs.tf
# Root module calling child modules
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
environment = var.environment
}
module "compute" {
source = "./modules/compute"
subnet_ids = module.vpc.private_subnet_ids
environment = var.environment
}
Key principle: Keep the module tree flat. Deeply nested modules (module calling module calling module) are hard to debug and reuse. Prefer composition at the root level.
See examples/modules.md for module versioning, registry sources, and internal vs published modules.
Pattern 7: Refactoring with moved, import, and removed Blocks
Refactor infrastructure without destroying resources.
# Rename a resource -- state updated, infrastructure untouched
moved {
from = aws_instance.web_server
to = aws_instance.app_server
}
# Adopt existing infrastructure into Terraform management
import {
to = aws_s3_bucket.logs
id = "my-existing-bucket-name"
}
# Remove from Terraform management without destroying the resource
removed {
from = aws_instance.legacy
lifecycle {
destroy = false
}
}
Always run terraform plan after adding these blocks to verify Terraform interprets the refactoring correctly. Look for "move" and "import" messages in the plan output.
See examples/state.md for module refactoring with moved blocks and bulk imports.
Pattern 8: Lifecycle Meta-Arguments
Control how Terraform manages resource lifecycle.
resource "aws_db_instance" "main" {
# ... configuration ...
lifecycle {
prevent_destroy = true # Block accidental deletion
create_before_destroy = true # Zero-downtime replacement
ignore_changes = [tags] # External process manages tags
}
}
When to use each:
prevent_destroy-- databases, DNS zones, state buckets (critical resources)create_before_destroy-- load balancers, instances behind ASGs (zero-downtime)ignore_changes-- auto-scaling managed attributes, externally tagged resourcesreplace_triggered_by-- force replacement when a dependency changes that Terraform does not detect
See examples/patterns.md for lifecycle combinations and replace_triggered_by.
Pattern 9: Custom Conditions (Preconditions, Postconditions, Checks)
Validate assumptions before provisioning and guarantees after.
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
lifecycle {
precondition {
condition = data.aws_ami.ubuntu.architecture == "x86_64"
error_message = "AMI must be x86_64 architecture."
}
postcondition {
condition = self.public_ip != ""
error_message = "Instance must have a public IP assigned."
}
}
}
Precondition vs postcondition vs check:
precondition-- validates assumptions before creation (blocks plan)postcondition-- validates guarantees after creation (blocks apply)checkblock -- validates infrastructure state without blocking operations (warnings only)
See examples/patterns.md for check blocks and variable validation patterns.
Pattern 10: Dynamic Blocks
Generate repeated nested blocks from collections. Use sparingly -- overuse hurts readability.
resource "aws_security_group" "web" {
name = "web-sg"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
When to use: Reusable modules where the number of nested blocks varies per caller. When not to use: Write nested blocks literally when the set is small and fixed. Dynamic blocks cannot generate meta-argument blocks (lifecycle, provisioner).
See examples/patterns.md for dynamic block patterns with conditionals.
</patterns><red_flags>
RED FLAGS
High Priority:
- Missing
.terraform.lock.hclin version control -- different team members get different provider versions, causing plan drift and mysterious failures - Local state for shared infrastructure -- no locking means concurrent applies corrupt state; no remote backup means state loss is catastrophic
- Secrets in
.tfor.tfvarsfiles -- committed to version control, visible in state file; useTF_VAR_*environment variables or your secrets manager - Using
countfor non-identical resources -- removing an item shifts indices, destroying and recreating unrelated resources terraform applywithout reviewing the plan -- auto-approve in production is how you delete databases- Unpinned provider versions --
version = ">= 5.0"without an upper bound allows major version upgrades that break everything
Medium Priority:
depends_onwhen an expression reference suffices --depends_oncauses overly conservative plans; let Terraform infer dependencies from expressions- Deeply nested module trees -- modules calling modules calling modules are hard to debug; keep the tree flat and compose at the root
ignore_changes = all-- Terraform will never update the resource again, even for intentional changes; be specific about which attributes to ignore- No
descriptionon variables and outputs -- undocumented inputs/outputs make modules unusable for anyone but the author - Hardcoded values instead of variables -- makes modules non-reusable; parameterize anything that changes between environments
Gotchas & Edge Cases:
- Backend configuration cannot use variables, locals, or expressions -- values must be literal strings or passed via
-backend-configduringterraform init prevent_destroydoes not prevent destruction if you remove the resource block entirely -- it only preventsterraform destroyon the resource while the block existsfor_eachkeys must be known at plan time -- they cannot reference resource attributes that are computed during applymovedblocks are processed once duringterraform plan/apply-- remove them after the migration is applied to keep configuration cleanterraform statesubcommands (mv, rm, pull, push) bypass safety checks -- usemoved/removedblocks instead for auditable, reviewable refactoringdatasources are read during planning by default -- if they depend on resources being created in the same apply, usedepends_onto defer the readsensitive = trueon variables prevents the value from appearing in plan output but does NOT encrypt it in state -- state encryption is a separate concernterraform fmtonly formats.tffiles in the current directory -- useterraform fmt -recursiveto format all subdirectoriestoset()deduplicates -- if your list has duplicates,for_each = toset(var.list)silently drops them.tfvarsfiles are auto-loaded only if namedterraform.tfvarsor*.auto.tfvars-- other filenames require explicit-var-fileflag
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST pin provider versions with constraints in required_providers and commit .terraform.lock.hcl to version control)
(You MUST use a remote backend with state locking for any shared or production infrastructure)
(You MUST use for_each with a map/set for non-identical resources -- count causes index-shift destruction on removal)
(You MUST never store secrets in .tf files, .tfvars, or state -- use environment variables (TF_VAR_*) or your secrets manager)
(You MUST run terraform plan and review the diff before every terraform apply -- never apply blindly)
Failure to follow these rules will cause state corruption, accidental resource destruction, secret exposure, and non-reproducible infrastructure.
</critical_reminders>
What ships with it: 5 files
41.6 KB alongside SKILL.md
examples/
- core.md8.4 KB
- modules.md6.8 KB
- patterns.md9.8 KB
- state.md8.1 KB
- reference.md8.6 KB