✓ Verified 💻 Development ✓ Enhanced Data

Infra As Code

Define and manage cloud infrastructure with code.

Rating
4.1 (155 reviews)
Downloads
28,032 downloads
Version
1.0.0

Overview

Define and manage cloud infrastructure with code.

Complete Documentation

View Source →

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

text
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.

Installation

Terminal bash

openclaw install infra-as-code
    
Copied!

💻Code Examples

terraform apply -var-file="env/$ENV.tfvars"

terraform-apply--var-fileenvenvtfvars.txt
## AWS CloudFormation

### Template Structure

aws cloudformation describe-stack-events --stack-name myapp-dev | head -50

aws-cloudformation-describe-stack-events---stack-name-myapp-dev--head--50.txt
## Pulumi (Code-First IaC)

### Quick Start (TypeScript)

const vpcId = infra.getOutput("vpcId");

const-vpcid--infragetoutputvpcid.txt
## Debugging Infrastructure

### Terraform plan issues
example.sh
# 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
example.txt
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
example.txt
# 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"
    }
  }
}
example.txt
# 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
}
example.txt
# 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"
}
example.txt
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-*"]
  }
}
example.txt
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]
}

Tags

#coding_agents-and-ides #code

Quick Info

Category Development
Model Claude 3.5
Complexity One-Click
Author gitgoodordietrying
Last Updated 3/10/2026
🚀
Optimized for
Claude 3.5
🧠

Ready to Install?

Get started with this skill in seconds

openclaw install infra-as-code