Manages cloud infrastructure using Terraform (and OpenTofu). Use when the user wants to write Terraform configurations, provision cloud resources, manage state, create modules, set up multi-environment deployments, import existing infrastructure, debug plan/apply errors, migrate between providers, or implement IaC best practices. Trigger words: terraform, opentofu, infrastructure as code, IaC, tf, hcl, terraform module, terraform state, terraform plan, terraform apply, terraform import, cloud provisioning, AWS terraform, GCP terraform, Azure terraform.
Writes, reviews, and manages Terraform/OpenTofu configurations for cloud infrastructure. Covers resource definitions, modules, state management, multi-environment setups, CI/CD integration, drift detection, and migration from manual infrastructure to code. Supports AWS, GCP, Azure, and other providers.
Standard Terraform project layout:
infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf # Environment-specific config
│ │ ├── variables.tf # Environment variables
│ │ ├── terraform.tfvars # Variable values
│ │ └── backend.tf # State backend config
│ ├── staging/
│ └── production/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/
│ ├── database/
│ └── monitoring/
├── shared/
│ └── providers.tf # Provider version constraints
└── README.md
For smaller projects, a flat structure is acceptable:
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
└── backend.tf
Follow these conventions:
# Use meaningful resource names that describe purpose
resource "aws_instance" "api_server" {
ami = var.ami_id
instance_type = var.instance_type
# Group related arguments
vpc_security_group_ids = [aws_security_group.api.id]
subnet_id = module.networking.private_subnet_ids[0]
# Tags on everything
tags = merge(var.common_tags, {
Name = "${var.project}-api-${var.environment}"
Role = "api-server"
})
# Lifecycle rules when needed
lifecycle {
create_before_destroy = true
ignore_changes = [ami] # AMI updated by CI/CD
}
}
Naming conventions:
snake_case, descriptive (web_server not ws1)snake_case, prefixed by component when ambiguous (db_instance_type)snake_case, prefixed by module name in root (networking_vpc_id)networking.tf, compute.tf, database.tf)Always define type, description, and validation:
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 API server"
default = "t3.medium"
validation {
condition = can(regex("^t3\\.", var.instance_type))
error_message = "Only t3 instance types are allowed for cost control."
}
}
# Use locals for computed values
locals {
name_prefix = "${var.project}-${var.environment}"
is_prod = var.environment == "production"
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
Team = var.team
}
}
Write reusable modules for repeated patterns:
# modules/ecs-service/variables.tf
variable "name" {
type = string
description = "Service name"
}
variable "container_image" {
type = string
description = "Docker image URI"
}
variable "cpu" {
type = number
default = 256
}
variable "memory" {
type = number
default = 512
}
# modules/ecs-service/main.tf
resource "aws_ecs_task_definition" "this" {
family = var.name
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = var.cpu
memory = var.memory
container_definitions = jsonencode([{
name = var.name
image = var.container_image
essential = true
portMappings = [{
containerPort = var.container_port
protocol = "tcp"
}]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.this.name
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = var.name
}
}
}])
}
# Usage in root
module "api" {
source = "./modules/ecs-service"
name = "api"
container_image = "123456.dkr.ecr.us-east-1.amazonaws.com/api:latest"
cpu = 512
memory = 1024
}
Module guidelines:
source = "git::https://...?ref=v1.2.0"Remote state (required for teams):
# AWS S3 backend
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "env/production/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
State operations:
# List all resources in state
terraform state list
# Show details of a resource
terraform state show aws_instance.api_server
# Move a resource (rename without recreate)
terraform state mv aws_instance.old_name aws_instance.new_name
# Import existing infrastructure
terraform import aws_instance.api_server i-0abc123def456
# Remove from state (without destroying)
terraform state rm aws_instance.temp_server
State safety:
terraform plan before every applyOption A: Workspaces (simple, same config):
terraform workspace new staging
terraform workspace select production
terraform apply -var-file="production.tfvars"
Option B: Directory per environment (recommended, different configs):
environments/dev/ → smaller instances, single AZ
environments/staging/ → mirrors prod at smaller scale
environments/prod/ → full HA, multi-AZ, larger instances
Each environment references shared modules with different variable values.
Option C: Terragrunt (DRY multi-environment):
# terragrunt.hcl
terraform {
source = "../../modules//networking"
}
inputs = {
environment = "production"
vpc_cidr = "10.0.0.0/16"
az_count = 3
}
For brownfield environments:
# 1. Write the resource block first
# 2. Import the real resource into state
terraform import aws_vpc.main vpc-0abc123
# 3. Run plan to see drift
terraform plan
# 4. Adjust config until plan shows no changes
Terraform 1.5+ supports import blocks:
import {
to = aws_instance.api_server
id = "i-0abc123def456"
}
Generate config automatically:
terraform plan -generate-config-out=generated.tf
# GitHub Actions
- name: Terraform Plan
run: |
terraform init
terraform plan -out=tfplan -no-color
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplan
Best practices for CI/CD:
plan on PRs, apply only on merge to main-out=tfplan to ensure apply matches the reviewed planinfracost breakdown --path ..tfvars files with secrets — use environment variables or a secrets managersensitive = true on variables containing secretstfsec, checkov, or trivy configrequired_providers { aws = { version = "~> 5.0" } }Input: "Set up a production VPC with public/private subnets across 3 AZs, a NAT gateway, an ECS Fargate cluster running our API service behind an ALB, and a PostgreSQL RDS instance in the private subnet. Include security groups that only allow necessary traffic."
Output: Complete Terraform config with:
Input: "We're running on both AWS and GCP. Create Terraform modules for: a Kubernetes cluster (EKS on AWS, GKE on GCP), a managed database (RDS on AWS, Cloud SQL on GCP), and a CDN (CloudFront on AWS, Cloud CDN on GCP). Each module should have the same interface so we can swap providers."
Output: Standardized modules with identical input/output interfaces:
modules/kubernetes/aws/ and modules/kubernetes/gcp/ — both accept cluster_name, node_count, node_type, output cluster_endpoint, kubeconfigmodules/database/aws/ and modules/database/gcp/ — both accept engine, size, output connection_stringmodules/cdn/aws/ and modules/cdn/gcp/ — both accept origin_url, domain, output cdn_urlcloud_provider = "aws" or "gcp"terraform fmt before committing — consistent formattingterraform validate to catch syntax errors earlyterraform plan as a review tool — never apply without reviewing the planfor_each over count — it handles additions/removals without index shiftingdata sources to reference existing resources, not hardcoded IDsmoved blocks for refactoring without destroying resourcesterraform-docs to auto-generate module documentationnpx skills add TerminalSkills/terraform-iac下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer