Agent skill
terraform
HashiCorp Terraform Infrastructure as Code (IaC) tool for provisioning, managing, and versioning cloud infrastructure
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/terraform-enuno-claude-command-and-c
SKILL.md
Terraform Infrastructure as Code Skill
Comprehensive assistance with HashiCorp Terraform for infrastructure provisioning, configuration management, and infrastructure lifecycle automation across cloud providers and on-premises environments.
When to Use This Skill
Explicit Triggers:
- When user says "create terraform configuration", "write terraform code", or "terraform plan/apply"
- When working with
.tf,.tfvars, or.tfstatefiles - When managing cloud resources (AWS, Azure, GCP, Kubernetes, etc.) declaratively
- When discussing infrastructure state management, modules, or providers
- When importing existing infrastructure or migrating to Terraform
- When debugging Terraform errors, state drift, or dependency issues
Context Indicators:
- Mentions of
resource,data,module,provider,variable,outputblocks - Infrastructure provisioning workflows (create, update, destroy)
- State management operations (state list, state show, state mv, state rm)
- Module development or registry usage
- Multi-environment infrastructure (dev, staging, production)
When NOT to Use This Skill
- When writing CloudFormation, Pulumi, or other IaC tools (different syntax/semantics)
- When managing Kubernetes manifests directly (use kubectl or Helm skills)
- When writing Ansible playbooks (configuration management, not IaC)
- When using Terraform Cloud/Enterprise (platform-specific features)
- When working with Terraform CDK (TypeScript/Python abstraction layer)
Prerequisites
Knowledge Requirements
- Understanding of target infrastructure platform (AWS, Azure, GCP, etc.)
- Basic networking concepts (VPC, subnets, security groups, load balancers)
- Resource dependencies and lifecycle management
- State management and locking concepts
Environment Setup
- Terraform CLI installed (
terraform version≥ 1.0) - Cloud provider credentials configured (AWS CLI, Azure CLI, gcloud, etc.)
- Backend configured for state storage (S3, Azure Storage, Terraform Cloud)
- Version control system (Git) for
.tffiles
Project Structure
terraform-project/
├── main.tf # Primary resource definitions
├── variables.tf # Input variable declarations
├── outputs.tf # Output value definitions
├── terraform.tfvars # Variable values (gitignored if sensitive)
├── versions.tf # Terraform and provider version constraints
├── backend.tf # State backend configuration
└── modules/ # Reusable module definitions
└── networking/
├── main.tf
├── variables.tf
└── outputs.tf
Core Terraform Workflow
Step 1: Initialize Terraform
# Download providers and initialize backend
terraform init
# Reinitialize if backend config changes
terraform init -reconfigure
# Upgrade providers to latest versions
terraform init -upgrade
What happens:
- Downloads provider plugins (AWS, Azure, GCP, etc.)
- Initializes backend for state storage
- Creates
.terraform/directory and lock file
Step 2: Write Configuration
Resource Block (creates infrastructure):
resource "aws_instance" "web_server" {
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = "web-server-${var.environment}"
Environment = var.environment
ManagedBy = "Terraform"
}
# Lifecycle management
lifecycle {
create_before_destroy = true
prevent_destroy = false
}
}
Data Source (queries existing infrastructure):
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
Variable Declaration:
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
validation {
condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
error_message = "Instance type must be t3.micro, t3.small, or t3.medium."
}
}
Output Values:
output "instance_public_ip" {
description = "Public IP address of the web server"
value = aws_instance.web_server.public_ip
sensitive = false
}
Step 3: Plan Changes
# Preview infrastructure changes
terraform plan
# Save plan to file for review
terraform plan -out=tfplan
# Plan for specific target resource
terraform plan -target=aws_instance.web_server
# Show plan with variable values
terraform plan -var="environment=production"
Plan Output Analysis:
+= Resource will be created-= Resource will be destroyed~= Resource will be modified in-place-/+= Resource will be destroyed and recreated<== Data source will be read
Step 4: Apply Changes
# Apply changes (requires confirmation)
terraform apply
# Apply saved plan (no confirmation needed)
terraform apply tfplan
# Auto-approve (use with caution)
terraform apply -auto-approve
# Apply with variable overrides
terraform apply -var="instance_type=t3.small"
Step 5: Verify and Inspect
# List all resources in state
terraform state list
# Show detailed resource state
terraform state show aws_instance.web_server
# Show all outputs
terraform output
# Show specific output value
terraform output instance_public_ip
Step 6: Destroy Infrastructure
# Destroy all managed infrastructure
terraform destroy
# Destroy specific resource
terraform destroy -target=aws_instance.web_server
# Preview destruction plan
terraform plan -destroy
Advanced Terraform Patterns
Module Usage
Calling a Module:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}
# Reference module outputs
resource "aws_instance" "app" {
subnet_id = module.vpc.private_subnets[0]
# ...
}
Creating a Module (modules/compute/main.tf):
variable "instance_count" {
type = number
default = 1
}
variable "subnet_ids" {
type = list(string)
}
resource "aws_instance" "this" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = element(var.subnet_ids, count.index)
tags = {
Name = "instance-${count.index + 1}"
}
}
output "instance_ids" {
value = aws_instance.this[*].id
}
Dynamic Blocks
resource "aws_security_group" "this" {
name = "web-sg"
vpc_id = var.vpc_id
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
}
}
}
For Each and Count
for_each (map or set):
resource "aws_s3_bucket" "this" {
for_each = toset(var.bucket_names)
bucket = each.value
tags = {
Name = each.value
}
}
count (numeric iteration):
resource "aws_instance" "web" {
count = var.instance_count
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = "web-${count.index + 1}"
}
}
Conditional Resources
resource "aws_eip" "this" {
count = var.create_eip ? 1 : 0
instance = aws_instance.web.id
domain = "vpc"
}
Dependencies
Implicit Dependencies (automatic):
resource "aws_instance" "web" {
subnet_id = aws_subnet.main.id # Implicit dependency
}
Explicit Dependencies (depends_on):
resource "aws_instance" "web" {
# ...
depends_on = [
aws_security_group.allow_web,
aws_subnet.main
]
}
State Management
State Commands
# List resources in state
terraform state list
# Show resource details
terraform state show aws_instance.web_server
# Move resource to different address
terraform state mv aws_instance.old aws_instance.new
# Remove resource from state (doesn't destroy)
terraform state rm aws_instance.decommissioned
# Pull remote state to local file
terraform state pull > terraform.tfstate.backup
# Push local state to remote backend
terraform state push terraform.tfstate
Remote State Backend (S3 + DynamoDB)
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
State Locking
- Prevents concurrent modifications (race conditions)
- DynamoDB table (AWS), Azure Storage, or Consul for locking
- Force unlock if lock stuck:
terraform force-unlock <lock-id>
State Drift Detection
# Detect drift between state and real infrastructure
terraform plan -refresh-only
# Apply drift detection (update state without changes)
terraform apply -refresh-only
Importing Existing Infrastructure
Import Workflow
# Step 1: Write resource configuration (without computed attributes)
cat > imported.tf <<'EOF'
resource "aws_instance" "imported_server" {
# ami and instance_type will be populated after import
}
EOF
# Step 2: Import existing resource into state
terraform import aws_instance.imported_server i-1234567890abcdef0
# Step 3: Run plan to see differences
terraform plan
# Step 4: Update configuration to match actual infrastructure
# (Copy values from plan output)
# Step 5: Verify no changes needed
terraform plan # Should show "No changes"
Bulk Import with Generated Code
# Generate configuration from existing resources (Terraform 1.5+)
terraform plan -generate-config-out=generated.tf
# Review and edit generated configuration
code generated.tf
# Apply to sync state
terraform apply
Provider Configuration
Multiple Provider Instances (Aliases)
provider "aws" {
region = "us-west-2"
}
provider "aws" {
alias = "east"
region = "us-east-1"
}
resource "aws_instance" "west" {
provider = aws # Default provider
# ...
}
resource "aws_instance" "east" {
provider = aws.east # Aliased provider
# ...
}
Provider Version Constraints
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # >= 5.0, < 6.0
}
random = {
source = "hashicorp/random"
version = ">= 3.5.0"
}
}
}
Functions and Expressions
Common Built-in Functions
String Functions:
# String interpolation
name = "server-${var.environment}-${var.region}"
# String manipulation
upper("hello") # "HELLO"
lower("WORLD") # "world"
title("hello world") # "Hello World"
trimspace(" text ") # "text"
format("instance-%03d", 5) # "instance-005"
Collection Functions:
# List operations
length([1, 2, 3]) # 3
concat([1, 2], [3, 4]) # [1, 2, 3, 4]
element(["a", "b", "c"], 1) # "b"
contains(["a", "b"], "a") # true
distinct([1, 2, 2, 3]) # [1, 2, 3]
# Map operations
merge({a = 1}, {b = 2}) # {a = 1, b = 2}
keys({a = 1, b = 2}) # ["a", "b"]
values({a = 1, b = 2}) # [1, 2]
lookup({a = 1}, "b", "default") # "default"
Type Conversion:
tostring(42) # "42"
tonumber("42") # 42
tobool("true") # true
tolist(toset([1, 2])) # [1, 2]
tomap({a = 1}) # {a = 1}
Filesystem Functions:
file("${path.module}/userdata.sh") # Read file content
templatefile("${path.module}/config.tpl", { # Render template
port = 8080
})
fileexists("${path.module}/optional.txt") # true/false
Date/Time:
timestamp() # "2025-12-31T12:00:00Z"
formatdate("YYYY-MM-DD", timestamp()) # "2025-12-31"
Conditional Expressions
# Ternary operator
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
# Null coalescing
ami_id = var.custom_ami != "" ? var.custom_ami : data.aws_ami.default.id
For Expressions
# Transform list
instance_names = [for i in aws_instance.web : i.tags.Name]
# Transform map
instance_ips = {for k, v in aws_instance.web : k => v.private_ip}
# Filter with condition
prod_instances = [for i in aws_instance.all : i if i.tags.Environment == "prod"]
Splat Expressions
# Equivalent to: [for i in aws_instance.web : i.id]
instance_ids = aws_instance.web[*].id
# With nested attributes
subnet_ids = aws_subnet.private[*].id
Workspaces (Multi-Environment)
Workspace Commands
# List workspaces
terraform workspace list
# Create new workspace
terraform workspace new staging
# Switch workspace
terraform workspace select production
# Show current workspace
terraform workspace show
# Delete workspace
terraform workspace delete dev
Workspace-Aware Configuration
resource "aws_instance" "web" {
instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
tags = {
Environment = terraform.workspace
Name = "web-${terraform.workspace}"
}
}
Troubleshooting Common Issues
Issue 1: Provider Authentication Errors
Error: error configuring Terraform AWS Provider: no valid credential sources
Solution:
# Set AWS credentials
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_DEFAULT_REGION="us-west-2"
# Or use AWS CLI profile
export AWS_PROFILE="terraform"
# Verify credentials
aws sts get-caller-identity
Issue 2: State Lock Errors
Error: Error acquiring the state lock
Solution:
# Check who holds the lock in DynamoDB table
aws dynamodb get-item --table-name terraform-lock --key '{"LockID": {"S": "my-state-file"}}'
# Force unlock (use with caution)
terraform force-unlock <lock-id>
Issue 3: Resource Already Exists
Error: creating EC2 Instance: InvalidParameterValue: Instance already exists
Solution:
# Import existing resource
terraform import aws_instance.web i-1234567890abcdef0
# Or remove from state if managed elsewhere
terraform state rm aws_instance.web
Issue 4: Dependency Cycle
Error: Cycle: aws_security_group.app -> aws_instance.web -> aws_security_group.app
Solution:
# Break cycle by removing circular dependency
# Option 1: Use separate ingress rules
resource "aws_security_group_rule" "app_to_web" {
type = "ingress"
security_group_id = aws_security_group.web.id
source_security_group_id = aws_security_group.app.id
from_port = 80
to_port = 80
protocol = "tcp"
}
Issue 5: Missing Required Argument
Error: Missing required argument: subnet_id
Solution:
# Add missing required argument
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
subnet_id = aws_subnet.main.id # Add this
}
Best Practices
Code Organization
✅ DO:
- Use consistent naming conventions (
snake_casefor resources,kebab-casefor files) - Group related resources in same file (networking.tf, compute.tf, storage.tf)
- Use modules for reusable components
- Pin provider versions in
versions.tf - Store sensitive values in
.tfvars(gitignored) or use secrets manager
❌ DON'T:
- Hardcode values (use variables)
- Mix resource types in single file (poor organization)
- Use
countandfor_eachon same resource - Skip documentation (use
descriptionfields) - Commit
.tfvarsfiles with secrets
State Management
✅ DO:
- Use remote backend (S3, Azure Storage, Terraform Cloud)
- Enable state locking (DynamoDB, Azure Storage)
- Enable state encryption (
encrypt = true) - Use separate states for different environments
- Back up state regularly
❌ DON'T:
- Edit state files manually
- Commit state files to version control
- Share state files insecurely
- Use local state for production
Security
✅ DO:
- Use least-privilege IAM policies
- Enable encryption (EBS, S3, RDS)
- Use security groups/network ACLs restrictively
- Rotate credentials regularly
- Use AWS Secrets Manager/Parameter Store for secrets
❌ DON'T:
- Hardcode credentials in
.tffiles - Use overly permissive security rules (0.0.0.0/0)
- Commit
.tfvarswith sensitive data - Disable encryption to simplify config
Version Control
✅ DO:
- Commit all
.tffiles - Commit
.terraform.lock.hcl(provider version locking) - Use
.gitignorefor sensitive files - Use feature branches for changes
- Review plans before applying
❌ DON'T:
- Commit
.terraform/directory - Commit
terraform.tfstateor.tfvarswith secrets - Apply changes without peer review
- Skip
terraform planbeforeapply
Examples
Example 1: AWS VPC with Public/Private Subnets
Scenario: Create production-grade VPC with high availability across 3 AZs.
main.tf:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project}-vpc"
Environment = var.environment
ManagedBy = "Terraform"
}
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project}-public-${count.index + 1}"
Type = "public"
}
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "${var.project}-private-${count.index + 1}"
Type = "private"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project}-igw"
}
}
resource "aws_eip" "nat" {
count = length(var.public_subnet_cidrs)
domain = "vpc"
tags = {
Name = "${var.project}-nat-eip-${count.index + 1}"
}
}
resource "aws_nat_gateway" "main" {
count = length(var.public_subnet_cidrs)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.project}-nat-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project}-public-rt"
}
}
resource "aws_route_table" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${var.project}-private-rt-${count.index + 1}"
}
}
resource "aws_route_table_association" "public" {
count = length(var.public_subnet_cidrs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = length(var.private_subnet_cidrs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
data "aws_availability_zones" "available" {
state = "available"
}
variables.tf:
variable "aws_region" {
description = "AWS region for resources"
type = string
default = "us-west-2"
}
variable "project" {
description = "Project name for resource naming"
type = string
}
variable "environment" {
description = "Environment (dev, staging, prod)"
type = string
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
description = "CIDR blocks for public subnets"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
variable "private_subnet_cidrs" {
description = "CIDR blocks for private subnets"
type = list(string)
default = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
}
outputs.tf:
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "Private subnet IDs"
value = aws_subnet.private[*].id
}
output "nat_gateway_ips" {
description = "NAT Gateway public IPs"
value = aws_eip.nat[*].public_ip
}
terraform.tfvars:
project = "myapp"
environment = "production"
aws_region = "us-west-2"
Execution:
terraform init
terraform plan -out=tfplan
terraform apply tfplan
Rationale: This example demonstrates production-grade networking with high availability, proper routing, and NAT gateways for private subnet internet access.
Example 2: Auto Scaling Web Application
Scenario: Deploy web application with auto-scaling, load balancer, and RDS database.
compute.tf:
resource "aws_launch_template" "web" {
name_prefix = "${var.project}-web-"
image_id = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.web.id]
user_data = base64encode(templatefile("${path.module}/userdata.sh", {
db_endpoint = aws_db_instance.main.endpoint
app_version = var.app_version
}))
iam_instance_profile {
name = aws_iam_instance_profile.web.name
}
monitoring {
enabled = true
}
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.project}-web"
Environment = var.environment
}
}
}
resource "aws_autoscaling_group" "web" {
name = "${var.project}-web-asg"
vpc_zone_identifier = var.private_subnet_ids
target_group_arns = [aws_lb_target_group.web.arn]
health_check_type = "ELB"
min_size = var.asg_min_size
max_size = var.asg_max_size
desired_capacity = var.asg_desired_capacity
launch_template {
id = aws_launch_template.web.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.project}-web-asg"
propagate_at_launch = true
}
}
resource "aws_autoscaling_policy" "scale_up" {
name = "${var.project}-scale-up"
autoscaling_group_name = aws_autoscaling_group.web.name
adjustment_type = "ChangeInCapacity"
scaling_adjustment = 1
cooldown = 300
}
resource "aws_cloudwatch_metric_alarm" "high_cpu" {
alarm_name = "${var.project}-high-cpu"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 120
statistic = "Average"
threshold = 80
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.web.name
}
alarm_actions = [aws_autoscaling_policy.scale_up.arn]
}
resource "aws_lb" "web" {
name = "${var.project}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
enable_deletion_protection = var.environment == "prod" ? true : false
}
resource "aws_lb_target_group" "web" {
name = "${var.project}-web-tg"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 5
interval = 30
path = "/health"
matcher = "200"
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.web.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
database.tf:
resource "aws_db_subnet_group" "main" {
name = "${var.project}-db-subnet-group"
subnet_ids = var.private_subnet_ids
}
resource "aws_db_instance" "main" {
identifier = "${var.project}-db"
engine = "postgres"
engine_version = "15.4"
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password # Use AWS Secrets Manager in production
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
backup_retention_period = var.environment == "prod" ? 7 : 1
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
skip_final_snapshot = var.environment != "prod"
tags = {
Name = "${var.project}-db"
Environment = var.environment
}
}
Rationale: Demonstrates auto-scaling patterns, load balancing, health checks, and RDS integration for production workloads.
Example 3: Multi-Region Disaster Recovery
Scenario: Deploy application in two regions with Route53 failover.
main.tf (us-west-2):
module "primary_region" {
source = "./modules/regional-stack"
providers = {
aws = aws.primary
}
region = "us-west-2"
is_primary = true
project = var.project
environment = var.environment
}
module "secondary_region" {
source = "./modules/regional-stack"
providers = {
aws = aws.secondary
}
region = "us-east-1"
is_primary = false
project = var.project
environment = var.environment
}
resource "aws_route53_zone" "main" {
name = var.domain_name
}
resource "aws_route53_record" "primary" {
zone_id = aws_route53_zone.main.zone_id
name = var.domain_name
type = "A"
set_identifier = "primary"
failover_routing_policy {
type = "PRIMARY"
}
alias {
name = module.primary_region.alb_dns_name
zone_id = module.primary_region.alb_zone_id
evaluate_target_health = true
}
health_check_id = aws_route53_health_check.primary.id
}
resource "aws_route53_record" "secondary" {
zone_id = aws_route53_zone.main.zone_id
name = var.domain_name
type = "A"
set_identifier = "secondary"
failover_routing_policy {
type = "SECONDARY"
}
alias {
name = module.secondary_region.alb_dns_name
zone_id = module.secondary_region.alb_zone_id
evaluate_target_health = true
}
}
resource "aws_route53_health_check" "primary" {
fqdn = module.primary_region.alb_dns_name
port = 80
type = "HTTP"
resource_path = "/health"
failure_threshold = 3
request_interval = 30
}
Rationale: Multi-region deployment with automatic DNS failover for high availability and disaster recovery.
Quality Standards
Configuration Quality:
- All resources have meaningful names and tags
- Variables use descriptive names and include descriptions
- Outputs document what values they expose
- Provider versions are pinned
- Remote backend is configured for production
- State locking is enabled
- Sensitive values use
sensitive = true
Code Organization:
- Related resources grouped in logical files
- Modules used for reusable components
- Consistent naming conventions followed
- No hardcoded values (use variables)
- Comments explain complex logic
Security:
- IAM policies follow least privilege
- Encryption enabled for data at rest/transit
- Security groups are restrictive
- No credentials in code
-
.tfvarswith secrets not committed
Operational:
-
terraform planshows expected changes - No state drift detected
- Destroy plan reviewed before execution
- Backup strategy defined
- Rollback procedure documented
Common Pitfalls
❌ Using count with for_each
# ERROR: Cannot use both
resource "aws_instance" "web" {
count = var.instance_count
for_each = toset(var.instance_names)
}
✅ Choose one iteration method
resource "aws_instance" "web" {
for_each = toset(var.instance_names)
# ...
}
❌ Editing state files manually
# DON'T: Edit terraform.tfstate directly
vim terraform.tfstate
✅ Use state commands
# DO: Use terraform state commands
terraform state rm aws_instance.old
terraform state mv aws_instance.old aws_instance.new
❌ Hardcoding credentials
# DON'T: Embed secrets
resource "aws_db_instance" "main" {
password = "MySecretPassword123!"
}
✅ Use secrets management
# DO: Reference secrets from external store
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db/password"
}
resource "aws_db_instance" "main" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
❌ Circular dependencies
# ERROR: A depends on B, B depends on A
resource "aws_security_group" "a" {
ingress {
security_groups = [aws_security_group.b.id]
}
}
resource "aws_security_group" "b" {
ingress {
security_groups = [aws_security_group.a.id]
}
}
✅ Break cycle with separate rules
resource "aws_security_group" "a" {}
resource "aws_security_group" "b" {}
resource "aws_security_group_rule" "a_to_b" {
security_group_id = aws_security_group.a.id
source_security_group_id = aws_security_group.b.id
}
resource "aws_security_group_rule" "b_to_a" {
security_group_id = aws_security_group.b.id
source_security_group_id = aws_security_group.a.id
}
Integration with Command & Control System
Related Skills:
- aws-skill: Cloud-specific AWS operations and best practices
- kubernetes-skill: Container orchestration (often Terraform-provisioned)
- git-workflow-skill: Version control for
.tffiles - ci-cd-skill: Automated Terraform pipelines
Related Commands:
/infrastructure-plan: Generate Terraform plan with architecture review/infrastructure-apply: Execute Terraform changes with approval gates/state-audit: Analyze Terraform state for drift and anomalies
MCP Dependencies:
- AWS MCP Server: Cloud provider integration for resource queries
- GitHub MCP Server: Version control for infrastructure code
- Secrets MCP Server: Secure credential management
Orchestration Notes:
- Can be chained with
aws-skillfor post-provisioning configuration - Invoked by
infrastructure-orchestratorfor multi-stage deployments - Outputs used as inputs for
kubernetes-skillcluster configuration
Version History
- 1.0.0 (2025-12-31): Initial comprehensive skill release
- Full Terraform workflow coverage (init, plan, apply, destroy)
- State management and remote backends
- Module patterns and advanced features
- Multi-region deployment examples
- Troubleshooting guide
- Best practices and security guidelines
Resources
Official Documentation
Learning Resources
Community
Updating This Skill
To refresh with latest Terraform features:
# Update config with new documentation sections
vim configs/terraform.json
# Regenerate skill
skill-seekers scrape --config configs/terraform.json
# Review and merge updates
diff output/terraform/SKILL.md INTEGRATION/incoming/terraform/SKILL.md
Skill Status: ✅ Production Ready Terraform Versions Supported: 1.0+ Last Updated: December 31, 2025 Maintained By: Claude Command & Control Team
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?