Infrastructure as Code
VerifiedDefine and manage cloud infrastructure with code. Use when writing Terraform, CloudFormation, or Pulumi configs, managing state, planning deployments, setting up networking/compute/storage resources, or debugging infrastructure drift.
$ Add to .claude/skills/ About This Skill
# Infrastructure as Code
Define, deploy, and manage cloud infrastructure using declarative configuration. Covers Terraform (multi-cloud), AWS CloudFormation, and Pulumi (code-first), with patterns for compute, networking, storage, databases, and state management.
When to Use
- Setting up cloud infrastructure (VPCs, EC2, Lambda, S3, RDS, etc.)
- Writing or modifying Terraform configurations
- Managing Terraform state (remote backends, workspaces, imports)
- Creating CloudFormation templates
- Using Pulumi for infrastructure in TypeScript/Python/Go
- Planning and previewing infrastructure changes safely
- Debugging drift between declared state and actual resources
- Setting up multi-environment deployments (dev/staging/prod)
Terraform
Quick Start
```bash # Install: https://developer.hashicorp.com/terraform/install
# Initialize a project mkdir infra && cd infra terraform init
# Core workflow terraform plan # Preview changes (safe, read-only) terraform apply # Apply changes (creates/modifies resources) terraform destroy # Tear down all resources
# Format and validate terraform fmt -recursive # Auto-format all .tf files terraform validate # Check syntax and config validity ```
Project Structure
``` infra/ main.tf # Primary resources variables.tf # Input variable declarations outputs.tf # Output values providers.tf # Provider configuration terraform.tfvars # Variable values (don't commit secrets) backend.tf # Remote state configuration modules/ vpc/ main.tf variables.tf outputs.tf compute/ main.tf variables.tf outputs.tf ```
Provider Configuration
```hcl # providers.tf terraform { required_version = ">= 1.5" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } }
provider "aws" { region = var.aws_region
default_tags { tags = { Project = var.project_name Environment = var.environment ManagedBy = "terraform" } } } ```
Variables and Outputs
```hcl # variables.tf variable "aws_region" { type = string default = "us-east-1" description = "AWS region for all resources" }
variable "environment" { type = string description = "Deployment environment" validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "Environment must be dev, staging, or prod." } }
variable "instance_type" { type = string default = "t3.micro" }
variable "db_password" { type = string sensitive = true description = "Database password (pass via TF_VAR_db_password env var)" }
# outputs.tf output "vpc_id" { value = aws_vpc.main.id description = "VPC ID" }
output "api_endpoint" { value = aws_lb.api.dns_name } ```
VPC + Networking
```hcl # Networking module resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_support = true enable_dns_hostnames = true
tags = { Name = "${var.project_name}-vpc" } }
resource "aws_subnet" "public" { count = 2 vpc_id = aws_vpc.main.id cidr_block = "10.0.${count.index + 1}.0/24" availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true tags = { Name = "${var.project_name}-public-${count.index + 1}" } }
resource "aws_subnet" "private" { count = 2 vpc_id = aws_vpc.main.id cidr_block = "10.0.${count.index + 10}.0/24" availability_zone = data.aws_availability_zones.available.names[count.index]
tags = { Name = "${var.project_name}-private-${count.index + 1}" } }
resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id }
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 } }
resource "aws_route_table_association" "public" { count = 2 subnet_id = aws_subnet.public[count.index].id route_table_id = aws_route_table.public.id }
resource "aws_security_group" "web" { name_prefix = "${var.project_name}-web-" vpc_id = aws_vpc.main.id
ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }
ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }
egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
data "aws_availability_zones" "available" { state = "available" } ```
Compute (EC2)
```hcl resource "aws_instance" "app" { ami = data.aws_ami.ubuntu.id instance_type = var.instance_type subnet_id = aws_subnet.public[0].id vpc_security_group_ids = [aws_security_group.web.id] key_name = var.key_pair_name
user_data = <<-EOF #!/bin/bash apt-get update apt-get install -y docker.io systemctl start docker docker run -d -p 80:8080 ${var.docker_image} EOF
tags = { Name = "${var.project_name}-app" } }
data "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical
filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-*-24.04-amd64-server-*"] } } ```
S3 + Static Website
```hcl resource "aws_s3_bucket" "website" { bucket = "${var.project_name}-website" }
resource "aws_s3_bucket_website_configuration" "website" { bucket = aws_s3_bucket.website.id
index_document { suffix = "index.html" } error_document { key = "error.html" } }
resource "aws_s3_bucket_public_access_block" "website" { bucket = aws_s3_bucket.website.id
block_public_acls = false block_public_policy = false ignore_public_acls = false restrict_public_buckets = false }
resource "aws_s3_bucket_policy" "website" { bucket = aws_s3_bucket.website.id
policy = jsonencode({ Version = "2012-10-17" Statement = [{ Sid = "PublicRead" Effect = "Allow" Principal = "*" Action = "s3:GetObject" Resource = "${aws_s3_bucket.website.arn}/*" }] })
depends_on = [aws_s3_bucket_public_access_block.website] } ```
RDS Database
```hcl resource "aws_db_subnet_group" "main" { name = "${var.project_name}-db" subnet_ids = aws_subnet.private[*].id }
resource "aws_security_group" "db" { name_prefix = "${var.project_name}-db-" vpc_id = aws_vpc.main.id
ingress { from_port = 5432 to_port = 5432 protocol = "tcp" security_groups = [aws_security_group.web.id] } }
resource "aws_db_instance" "main" { identifier = "${var.project_name}-db" engine = "postgres" engine_version = "16.1" instance_class = "db.t3.micro" allocated_storage = 20
db_name = var.db_name username = var.db_username password = var.db_password
db_subnet_group_name = aws_db_subnet_group.main.name vpc_security_group_ids = [aws_security_group.db.id]
backup_retention_period = 7 skip_final_snapshot = var.environment != "prod" deletion_protection = var.environment == "prod" } ```
Lambda Function
```hcl data "archive_file" "lambda_zip" { type = "zip" source_dir = "${path.module}/lambda" output_path = "${path.module}/lambda.zip" }
resource "aws_lambda_function" "api" { function_name = "${var.project_name}-api" filename = data.archive_file.lambda_zip.output_path source_code_hash = data.archive_file.lambda_zip.output_base64sha256 handler = "index.handler" runtime = "nodejs20.x" timeout = 30
role = aws_iam_role.lambda_exec.arn
environment { variables = { DB_HOST = aws_db_instance.main.endpoint ENVIRONMENT = var.environment } } }
resource "aws_iam_role" "lambda_exec" { name = "${var.project_name}-lambda-exec"
assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } }] }) }
resource "aws_iam_role_policy_attachment" "lambda_basic" { role = aws_iam_role.lambda_exec.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } ```
State Management
```hcl # backend.tf - Remote state in S3 terraform { backend "s3" { bucket = "my-terraform-state" key = "project/terraform.tfstate" region = "us-east-1" dynamodb_table = "terraform-locks" encrypt = true } } ```
```bash # State operations terraform state list # List all resources in state terraform state show aws_instance.app # Show resource details terraform state mv aws_instance.app aws_instance.web # Rename resource terraform state rm aws_instance.old # Remove from state (doesn't destroy)
# Import existing resource into Terraform terraform import aws_instance.app i-1234567890abcdef0
# Workspaces (multiple environments, same config) terraform workspace new dev terraform workspace new staging terraform workspace new prod terraform workspace select dev terraform workspace list ```
Multi-Environment Pattern
```hcl # Use workspaces + tfvars files # terraform.tfvars (default) # env/dev.tfvars # env/staging.tfvars # env/prod.tfvars
# Apply for specific environment # terraform apply -var-file=env/prod.tfvars ```
```bash # Environment-specific apply ENV=${1:-dev} terraform workspace select "$ENV" || terraform workspace new "$ENV" terraform apply -var-file="env/$ENV.tfvars" ```
AWS CloudFormation
Template Structure
```yaml # cloudformation.yaml AWSTemplateFormatVersion: '2010-09-09' Description: My application stack
Parameters: Environment: Type: String AllowedValues: [dev, staging, prod] Default: dev InstanceType: Type: String Default: t3.micro
Conditions: IsProd: !Equals [!Ref Environment, prod]
Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-vpc"
PublicSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.1.0/24 MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-public"
AppInstance: Type: AWS::EC2::Instance Properties: InstanceType: !Ref InstanceType SubnetId: !Ref PublicSubnet ImageId: !FindInMap [RegionAMI, !Ref "AWS::Region", ubuntu]
Database: Type: AWS::RDS::DBInstance Condition: IsProd DeletionPolicy: Snapshot Properties: Engine: postgres DBInstanceClass: db.t3.micro AllocatedStorage: 20 MasterUsername: admin MasterUserPassword: !Ref DBPassword
Outputs: VpcId: Value: !Ref VPC Export: Name: !Sub "${AWS::StackName}-VpcId" InstanceIP: Value: !GetAtt AppInstance.PublicIp ```
CloudFormation CLI
```bash # Validate template aws cloudformation validate-template --template-body file://cloudformation.yaml
# Create stack aws cloudformation create-stack \ --stack-name myapp-dev \ --template-body file://cloudformation.yaml \ --parameters ParameterKey=Environment,ParameterValue=dev \ --capabilities CAPABILITY_IAM
# Update stack aws cloudformation update-stack \ --stack-name myapp-dev \ --template-body file://cloudformation.yaml \ --parameters ParameterKey=Environment,ParameterValue=dev
# Preview changes (changeset) aws cloudformation create-change-set \ --stack-name myapp-dev \ --change-set-name update-1 \ --template-body file://cloudformation.yaml
aws cloudformation describe-change-set \ --stack-name myapp-dev \ --change-set-name update-1
# Delete stack aws cloudformation delete-stack --stack-name myapp-dev
# List stacks aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE
# Stack events (debugging) aws cloudformation describe-stack-events --stack-name myapp-dev | head -50 ```
Pulumi (Code-First IaC)
Quick Start (TypeScript)
```bash # Install: https://www.pulumi.com/docs/install/ pulumi new aws-typescript
# Core workflow pulumi preview # Preview changes pulumi up # Apply changes pulumi destroy # Tear down pulumi stack ls # List stacks ```
TypeScript Example
```typescript // index.ts import * as pulumi from "@pulumi/pulumi"; import * as aws from "@pulumi/aws";
const config = new pulumi.Config(); const environment = config.require("environment");
// VPC const vpc = new aws.ec2.Vpc("main", { cidrBlock: "10.0.0.0/16", enableDnsSupport: true, enableDnsHostnames: true, tags: { Name: `myapp-${environment}-vpc` }, });
// Public subnet const publicSubnet = new aws.ec2.Subnet("public", { vpcId: vpc.id, cidrBlock: "10.0.1.0/24", mapPublicIpOnLaunch: true, tags: { Name: `myapp-${environment}-public` }, });
// S3 bucket const bucket = new aws.s3.Bucket("data", { bucket: `myapp-${environment}-data`, versioning: { enabled: true }, });
// Lambda function const lambdaRole = new aws.iam.Role("lambda-role", { assumeRolePolicy: JSON.stringify({ Version: "2012-10-17", Statement: [{ Action: "sts:AssumeRole", Effect: "Allow", Principal: { Service: "lambda.amazonaws.com" }, }], }), });
const lambdaFunc = new aws.lambda.Function("api", { runtime: "nodejs20.x", handler: "index.handler", role: lambdaRole.arn, code: new pulumi.asset.FileArchive("./lambda"), environment: { variables: { BUCKET_NAME: bucket.id, ENVIRONMENT: environment, }, }, });
// Outputs export const vpcId = vpc.id; export const bucketName = bucket.id; export const lambdaArn = lambdaFunc.arn; ```
Python Example
```python # __main__.py import pulumi import pulumi_aws as aws
config = pulumi.Config() environment = config.require("environment")
vpc = aws.ec2.Vpc("main", cidr_block="10.0.0.0/16", enable_dns_support=True, enable_dns_hostnames=True, tags={"Name": f"myapp-{environment}-vpc"})
bucket = aws.s3.Bucket("data", bucket=f"myapp-{environment}-data", versioning=aws.s3.BucketVersioningArgs(enabled=True))
pulumi.export("vpc_id", vpc.id) pulumi.export("bucket_name", bucket.id) ```
Pulumi State and Stacks
```bash # Create per-environment stacks pulumi stack init dev pulumi stack init staging pulumi stack init prod
# Switch stack pulumi stack select dev
# Set config per stack pulumi config set environment dev pulumi config set aws:region us-east-1 pulumi config set --secret dbPassword 'my-secret-pass'
# Stack references (cross-stack) # In consuming stack: const infra = new pulumi.StackReference("org/infra/prod"); const vpcId = infra.getOutput("vpcId"); ```
Debugging Infrastructure
Terraform plan issues
```bash # Detailed plan output terraform plan -out=plan.tfplan terraform show plan.tfplan terraform show -json plan.tfplan | jq '.resource_changes[] | {address, change: .change.actions}'
# Debug mode TF_LOG=DEBUG terraform plan 2> debug.log
# Check for drift terraform plan -refresh-only
# Force refresh state terraform apply -refresh-only ```
Common issues
```bash # Resource stuck in "tainted" state terraform untaint aws_instance.app
# State locked (another apply running or crashed) terraform force-unlock LOCK_ID
# Provider version conflict terraform providers lock # Generate lock file terraform init -upgrade # Upgrade providers
# Circular dependency # Error: "Cycle" in terraform plan # Fix: use depends_on explicitly, or break the cycle with data sources ```
Cost estimation
```bash # Infracost (estimates monthly cost from Terraform plans) # Install: https://www.infracost.io/docs/ infracost breakdown --path . infracost diff --path . --compare-to infracost-base.json ```
Tips
- Always run `terraform plan` before `apply`. Read the plan output carefully — especially lines showing `destroy` or `replace`.
- Use remote state from day one. Local state files get lost, can't be shared, and have no locking.
- Tag everything. At minimum: Project, Environment, ManagedBy. Tags make cost tracking and cleanup possible.
- Never store secrets in `.tf` files or `terraform.tfvars`. Use environment variables (`TF_VAR_name`), secrets managers, or Vault.
- Use `prevent_destroy` lifecycle rules on stateful resources (databases, S3 buckets with data) to prevent accidental deletion.
- Pin provider versions (`~> 5.0` not `>= 5.0`) to avoid surprise breaking changes.
- For multi-environment setups, prefer workspaces + var files over duplicated configurations.
- CloudFormation change sets are the equivalent of `terraform plan` — always create one before updating a stack.
- Pulumi's advantage is using real programming languages (loops, conditionals, type checking). Use it when Terraform's HCL feels limiting.
Use Cases
- Write Terraform, CloudFormation, or Pulumi configurations for cloud infrastructure
- Manage infrastructure state and plan deployments across environments
- Set up networking, compute, and storage resources declaratively
- Automate cloud infrastructure provisioning with best-practice patterns
- Review and optimize Infrastructure as Code templates for security and cost
Pros & Cons
Pros
- +Compatible with multiple platforms including claude-code, openclaw
- +Well-documented with detailed usage instructions and examples
- +Strong community adoption with a large number of downloads
- +Open source with permissive licensing
Cons
- -No built-in analytics or usage metrics dashboard
- -Configuration may require familiarity with ai & machine learning concepts
FAQ
What does Infrastructure as Code do?
What platforms support Infrastructure as Code?
What are the use cases for Infrastructure as Code?
100+ free AI tools
Writing, PDF, image, and developer tools — all in your browser.
Next Step
Use the skill detail page to evaluate fit and install steps. For a direct browser workflow, move into a focused tool route instead of staying in broader support surfaces.