agentleFS
justanestajustanesta/claude-code-resources/skills/devops/devops-terraform-patterns/SKILL.md

devops-terraform-patterns

Skill2 starsChanged 10 months agoMITView on GitHub

Terraform infrastructure-as-code patterns covering module design, state management, resource lifecycle, variables, CI/CD integration, and migration strategies for AWS and GCP.

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use for_each over count when resources need stable identity.

    line 20
  2. Distinctive:

    Use typed variables with validation blocks.

    line 108
  3. Very specific:

    Import existing resources into Terraform management.

    line 163
  4. Very specific:

    After import, run terraform plan to verify no diff.

    line 182

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Terraform Patterns

Core Principles

  1. Infrastructure as Code — All infrastructure is declared in version-controlled .tf files. Manual changes are prohibited; every resource has a single source of truth in code.
  2. State Is Sacred — Remote state with locking prevents corruption. Never edit state files by hand. Use terraform state commands for manipulation and always back up before operations.
  3. Modular Design — Reusable modules encapsulate logical groupings of resources. Root modules compose child modules. Pin module versions to avoid surprises.
  4. Immutable Infrastructure — Prefer replacing resources over mutating them. Use create_before_destroy lifecycle rules for zero-downtime deployments.
  5. Least Privilege Plans — CI pipelines run plan on every PR and apply only on merge to main. Human review of plan output is mandatory before any production apply.

Resource and Data Source Patterns

Use for_each over count when resources need stable identity. Data sources fetch existing infrastructure without managing it.

# Fetch existing VPC, create subnets with for_each for stable keys
data "aws_vpc" "main" {
  filter {
    name   = "tag:Environment"
    values = [var.environment]
  }
}

resource "aws_subnet" "private" {
  for_each = var.private_subnets

  vpc_id            = data.aws_vpc.main.id
  cidr_block        = each.value.cidr
  availability_zone = each.value.az

  tags = merge(var.common_tags, {
    Name = "${var.project}-private-${each.key}"
    Tier = "private"
  })
}

See resource-patterns for: lifecycle rules, depends_on usage, count vs for_each trade-offs, dynamic blocks, and provisioner patterns.


Module Design

Modules accept typed inputs, expose outputs, and compose into larger stacks. Keep modules focused on a single concern.

# Root module composing child modules
module "networking" {
  source  = "git::https://github.com/org/terraform-modules.git//networking?ref=v2.3.1"

  environment    = var.environment
  vpc_cidr       = var.vpc_cidr
  private_subnets = var.private_subnets
  public_subnets  = var.public_subnets
}

module "database" {
  source = "./modules/rds"

  subnet_ids         = module.networking.private_subnet_ids
  security_group_ids = [module.networking.db_security_group_id]
  instance_class     = var.db_instance_class
  engine_version     = var.db_engine_version
}

output "db_endpoint" {
  description = "RDS instance endpoint"
  value       = module.database.endpoint
  sensitive   = true
}

See module-design-patterns for: module structure conventions, input validation, output design, versioning strategies, and registry publishing.


State Management

Store state remotely with locking. Use workspaces or directory-based separation for environments.

# S3 backend with DynamoDB locking
terraform {
  backend "s3" {
    bucket         = "myorg-terraform-state"
    key            = "prod/networking/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
    kms_key_id     = "alias/terraform-state"
  }
}

See state-management for: remote backend configuration (S3, GCS), locking mechanisms, workspace strategies, state migration, and disaster recovery.


Variables and Locals

Use typed variables with validation blocks. Locals reduce repetition and compute derived values.

variable "instance_type" {
  description = "EC2 instance type for the application tier"
  type        = string
  default     = "t3.medium"

  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "Only t3 instance types are allowed in this environment."
  }
}

locals {
  name_prefix = "${var.project}-${var.environment}"
  common_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
    CostCenter  = var.cost_center
  }
}

See variable-patterns for: complex type constraints, validation rules, locals patterns, conditional resource creation, and tfvars file organization.


Plan/Apply Workflow

Separate plan and apply stages. Save plan files for deterministic applies. Integrate with CI pipelines for automated review.

#!/usr/bin/env bash
set -euo pipefail

# Plan stage: generate and save binary plan
terraform init -backend-config="env/${ENVIRONMENT}.hcl"
terraform plan \
  -var-file="env/${ENVIRONMENT}.tfvars" \
  -out="tfplan-${ENVIRONMENT}-$(date +%s)" \
  -detailed-exitcode

# Exit code 2 means changes detected
# Apply stage: use saved plan for deterministic apply
terraform apply "tfplan-${ENVIRONMENT}-${PLAN_TIMESTAMP}"

See ci-integration for: GitHub Actions workflows, GitLab CI pipelines, plan artifact storage, auto-apply strategies, and Atlantis configuration.


Import and Migration Patterns

Import existing resources into Terraform management. Use import blocks (Terraform 1.5+) for declarative imports.

# Declarative import (Terraform 1.5+)
import {
  to = aws_s3_bucket.legacy_data
  id = "my-legacy-bucket-name"
}

resource "aws_s3_bucket" "legacy_data" {
  bucket = "my-legacy-bucket-name"

  tags = {
    ManagedBy = "terraform"
    Imported  = "true"
  }
}

After import, run terraform plan to verify no diff. Adjust the resource configuration until the plan shows no changes. For bulk imports, generate configuration with terraform plan -generate-config-out=generated.tf.


Anti-Patterns

AvoidUse Instead
count with maps or objects that may reorderfor_each with a stable map key
Hardcoded provider credentials in .tf filesEnvironment variables or IAM roles
Monolithic root module with hundreds of resourcesSmaller modules composed in a root module
terraform apply -auto-approve in productionSaved plan file reviewed then applied
Storing state locally or in version controlRemote backend with encryption and locking
Using terraform taint (deprecated)terraform apply -replace=RESOURCE
Wildcard provider version constraintsPinned versions with pessimistic constraint ~>
Nested provider blocks inside modulesPass providers from root module via providers argument
Running terraform destroy without targeted planterraform plan -destroy reviewed first
String interpolation for simple referencesDirect reference var.name instead of "${var.name}"

Performance

  • Parallelism — Increase with terraform apply -parallelism=20 for large stacks. Default is 10. Monitor API rate limits.
  • Targeted applies — Use -target=module.networking during development to reduce plan/apply time. Never rely on targets in production.
  • State splitting — Break monolithic state into smaller states per service or layer (networking, compute, data). Use terraform_remote_state data source for cross-stack references.
  • Provider caching — Configure plugin_cache_dir in ~/.terraformrc to avoid re-downloading providers across workspaces.
  • Plan file caching — In CI, cache .terraform directory between runs. Use terraform providers lock to generate lock files for consistent installs.
  • Refresh skipping — Use terraform plan -refresh=false when you know infrastructure has not changed externally. Reduces API calls significantly on large stacks.
  • Data source minimization — Avoid unnecessary data source lookups in tight loops. Cache results in locals when the same data source is referenced multiple times.

source: Terraform documentation, HashiCorp best practices, AWS/GCP provider guides

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.