agentsclimarketplace

Infra iac terraform

Skill agents-inc/skills/src/skills/infra-iac-terraform

The official skills marketplace for Agents Inc, an agent composition framework that builds stacks and compiles specialized subagents for Claude Code

Install
npx -y skills add agents-inc/skills --skill infra-iac-terraform

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 18 stars18 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.

What its author says it does

Copied from the file, not written here

Infrastructure as Code with HashiCorp Terraform

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_providers and commit .terraform.lock.hcl. Use remote backends with state locking for team collaboration. Prefer for_each over count for non-identical resources. Use moved blocks for refactoring, import blocks for adopting existing infrastructure. Validate inputs with validation blocks and infrastructure with precondition/postcondition. Keep modules flat, composable, and single-purpose. Run terraform fmt and terraform validate before 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, and removed blocks
  • 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, and removed blocks
  • Custom conditions (precondition, postcondition, check blocks)
  • 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.

</philosophy>
<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 resources
  • replace_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)
  • check block -- 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.hcl in 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 .tf or .tfvars files -- committed to version control, visible in state file; use TF_VAR_* environment variables or your secrets manager
  • Using count for non-identical resources -- removing an item shifts indices, destroying and recreating unrelated resources
  • terraform apply without 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_on when an expression reference suffices -- depends_on causes 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 description on 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-config during terraform init
  • prevent_destroy does not prevent destruction if you remove the resource block entirely -- it only prevents terraform destroy on the resource while the block exists
  • for_each keys must be known at plan time -- they cannot reference resource attributes that are computed during apply
  • moved blocks are processed once during terraform plan/apply -- remove them after the migration is applied to keep configuration clean
  • terraform state subcommands (mv, rm, pull, push) bypass safety checks -- use moved/removed blocks instead for auditable, reviewable refactoring
  • data sources are read during planning by default -- if they depend on resources being created in the same apply, use depends_on to defer the read
  • sensitive = true on variables prevents the value from appearing in plan output but does NOT encrypt it in state -- state encryption is a separate concern
  • terraform fmt only formats .tf files in the current directory -- use terraform fmt -recursive to format all subdirectories
  • toset() deduplicates -- if your list has duplicates, for_each = toset(var.list) silently drops them
  • .tfvars files are auto-loaded only if named terraform.tfvars or *.auto.tfvars -- other filenames require explicit -var-file flag

</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: 6 files

42.2 KB alongside SKILL.md

examples/

Keep looking

Skills are one crate of 327,132. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.