terraform-patterns

Infrastructure as Code with Terraform — project structure, remote state, modules, workspace strategy, AWS/GCP patterns, CI/CD integration, and security hardening. The standard for managing production infrastructure.

8 stars

Best use case

terraform-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Infrastructure as Code with Terraform — project structure, remote state, modules, workspace strategy, AWS/GCP patterns, CI/CD integration, and security hardening. The standard for managing production infrastructure.

Teams using terraform-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/terraform-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/marvinrichter/clarc/main/skills/terraform-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/terraform-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How terraform-patterns Compares

Feature / Agentterraform-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Infrastructure as Code with Terraform — project structure, remote state, modules, workspace strategy, AWS/GCP patterns, CI/CD integration, and security hardening. The standard for managing production infrastructure.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Terraform Patterns

Infrastructure as Code: describe desired state → Terraform plans the diff → apply. Never manually change infrastructure that Terraform manages.

## When to Activate

- Provisioning any cloud resource (compute, databases, networking, IAM)
- Setting up remote state backend for a team
- Creating reusable infrastructure modules
- Migrating manually-created resources under Terraform control
- Reviewing infrastructure changes in CI/CD
- Structuring multi-environment infrastructure (dev, staging, prod) with separate state files
- Debugging a `terraform plan` that shows unexpected resource replacements or drift
- Enforcing IAM least-privilege policies for ECS task roles or Kubernetes workloads

---

## Project Structure

```
infrastructure/
├── environments/
│   ├── dev/
│   │   ├── main.tf          # environment-specific resource composition
│   │   ├── variables.tf
│   │   ├── terraform.tfvars # non-secret values for dev
│   │   └── outputs.tf
│   ├── staging/
│   └── prod/
├── modules/
│   ├── vpc/                 # reusable VPC module
│   ├── ecs-service/         # reusable ECS service module
│   ├── rds-postgres/        # reusable RDS module
│   └── cdn/                 # CloudFront + S3 origin
└── shared/
    └── backend.tf           # shared state backend config
```

**Rule:** Every environment is an isolated Terraform root. `modules/` are reusable, parameterized building blocks. Never put resources directly in `modules/` — only in `environments/`.

---

## Remote State — Mandatory for Teams

State must be remote + locked. Local state in git = team conflicts and drift.

### AWS S3 + DynamoDB backend

```hcl
# infrastructure/environments/prod/backend.tf
terraform {
  backend "s3" {
    bucket         = "my-company-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "eu-west-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"  # prevents concurrent applies
  }
}
```

### Bootstrap the backend (one-time)

```hcl
# infrastructure/bootstrap/main.tf — apply this FIRST, before anything else
resource "aws_s3_bucket" "tf_state" {
  bucket = "my-company-terraform-state"
}

resource "aws_s3_bucket_versioning" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  versioning_configuration { status = "Enabled" }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  rule {
    apply_server_side_encryption_by_default { sse_algorithm = "AES256" }
  }
}

resource "aws_dynamodb_table" "tf_lock" {
  name         = "terraform-state-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"
  attribute {
    name = "LockID"
    type = "S"
  }
}
```

---

## Module Pattern

```hcl
# modules/rds-postgres/main.tf
variable "identifier"        { type = string }
variable "instance_class"    { type = string; default = "db.t4g.micro" }
variable "allocated_storage" { type = number; default = 20 }
variable "db_name"           { type = string }
variable "username"          { type = string }
variable "password"          { type = string; sensitive = true }
variable "subnet_ids"        { type = list(string) }
variable "vpc_id"            { type = string }
variable "ingress_sg_ids"    { type = list(string) }
variable "environment"       { type = string }

resource "aws_db_subnet_group" "this" {
  name       = "${var.identifier}-subnet-group"
  subnet_ids = var.subnet_ids
}

resource "aws_security_group" "rds" {
  name   = "${var.identifier}-rds-sg"
  vpc_id = var.vpc_id

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = var.ingress_sg_ids
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_db_instance" "this" {
  identifier           = var.identifier
  engine               = "postgres"
  engine_version       = "16"
  instance_class       = var.instance_class
  allocated_storage    = var.allocated_storage
  storage_encrypted    = true                    # always encrypt
  db_name              = var.db_name
  username             = var.username
  password             = var.password
  db_subnet_group_name = aws_db_subnet_group.this.name
  vpc_security_group_ids = [aws_security_group.rds.id]

  backup_retention_period = 7                    # 7-day PITR
  deletion_protection     = var.environment == "prod"
  skip_final_snapshot     = var.environment != "prod"

  multi_az               = var.environment == "prod"
  publicly_accessible    = false                 # never public

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

output "endpoint" { value = aws_db_instance.this.endpoint }
output "sg_id"    { value = aws_security_group.rds.id }
```

### Consuming the module

```hcl
# environments/prod/main.tf
module "postgres" {
  source = "../../modules/rds-postgres"

  identifier     = "myapp-prod"
  instance_class = "db.t4g.medium"
  db_name        = "myapp"
  username       = "myapp"
  password       = var.db_password   # from tfvars or secret manager
  subnet_ids     = module.vpc.private_subnet_ids
  vpc_id         = module.vpc.vpc_id
  ingress_sg_ids = [module.api.security_group_id]
  environment    = "prod"
}
```

---

## Variables and Secrets

```hcl
# variables.tf
variable "environment"  { type = string }
variable "region"       { type = string; default = "eu-west-1" }
variable "db_password"  {
  type      = string
  sensitive = true   # never shown in plan output or logs
}
```

```hcl
# terraform.tfvars (commit this — non-secret values only)
environment = "prod"
region      = "eu-west-1"
```

**Secrets strategy:**
```bash
# Option A: Pass at apply time (CI/CD)
terraform apply -var="db_password=$DB_PASSWORD"

# Option B: AWS Secrets Manager — fetch in Terraform
data "aws_secretsmanager_secret_version" "db" {
  secret_id = "myapp/prod/db-password"
}
# Then: jsondecode(data.aws_secretsmanager_secret_version.db.secret_string)["password"]

# Option C: Vault provider for HashiCorp Vault
```

**NEVER** put secrets in `terraform.tfvars` or hardcode them in `.tf` files.

---

## Workspace Strategy

```bash
# Workspaces are for lightweight environment separation
# within the SAME backend config (same bucket, different state key)

terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
terraform workspace select prod

# Reference current workspace in resources
resource "aws_s3_bucket" "uploads" {
  bucket = "myapp-${terraform.workspace}-uploads"
}
```

**When to use workspaces vs. separate directories:**
- **Workspaces**: same infra shape across envs (all have same resources, different sizes)
- **Separate dirs**: envs have fundamentally different resource sets (prod has WAF, dev doesn't)

---

## ECS + Fargate Pattern

```hcl
# modules/ecs-service/main.tf
resource "aws_ecs_task_definition" "this" {
  family                   = var.service_name
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = var.cpu
  memory                   = var.memory
  execution_role_arn       = aws_iam_role.execution.arn
  task_role_arn            = aws_iam_role.task.arn

  container_definitions = jsonencode([{
    name      = var.service_name
    image     = var.container_image
    essential = true
    portMappings = [{ containerPort = var.port, protocol = "tcp" }]
    environment = [for k, v in var.env_vars : { name = k, value = v }]
    secrets     = [for k, v in var.secrets : { name = k, valueFrom = v }]
    logConfiguration = {
      logDriver = "awslogs"
      options = {
        "awslogs-group"         = "/ecs/${var.service_name}"
        "awslogs-region"        = data.aws_region.current.name
        "awslogs-stream-prefix" = "ecs"
      }
    }
  }])
}

resource "aws_ecs_service" "this" {
  name            = var.service_name
  cluster         = var.cluster_arn
  task_definition = aws_ecs_task_definition.this.arn
  desired_count   = var.desired_count
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = var.private_subnet_ids
    security_groups  = [aws_security_group.service.id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.this.arn
    container_name   = var.service_name
    container_port   = var.port
  }

  deployment_circuit_breaker {
    enable   = true
    rollback = true   # auto-rollback on failed deployment
  }

  lifecycle {
    ignore_changes = [desired_count]  # let autoscaling manage count
  }
}
```

---

## IAM — Least Privilege

```hcl
# Task role: what the container can DO
resource "aws_iam_role_policy" "task" {
  name   = "${var.service_name}-task-policy"
  role   = aws_iam_role.task.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["s3:GetObject", "s3:PutObject"]
        Resource = "${var.uploads_bucket_arn}/*"
      },
      {
        Effect   = "Allow"
        Action   = ["secretsmanager:GetSecretValue"]
        Resource = var.db_secret_arn
      }
    ]
  })
}

# Never use AdministratorAccess or * resources on task roles
```

> For CI/CD integration, `terraform import`, common commands, anti-patterns, and IaC tool selection — see skill `terraform-ci`.

Related Skills

zero-trust-patterns

8
from marvinrichter/clarc

Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.

webrtc-patterns

8
from marvinrichter/clarc

WebRTC patterns — peer connection setup, ICE/STUN/TURN configuration, signaling server design, SFU vs mesh topology, screen sharing, media track management, and reconnect/ICE restart handling.

webhook-patterns

8
from marvinrichter/clarc

Webhook patterns for receiving, verifying (HMAC), and idempotently processing third-party events. Covers Stripe, GitHub, and generic webhook patterns, delivery guarantees, retry handling, and testing.

wasm-patterns

8
from marvinrichter/clarc

WebAssembly patterns: wasm-pack, wasm-bindgen (JS↔Wasm interop), WASI, Component Model, wasm-opt, Rust-to-WASM compilation, JS integration (web workers, streaming instantiation), and production deployment (CDN, Content-Type headers).

ux-micro-patterns

8
from marvinrichter/clarc

UX micro-patterns for every product state: Empty States, Loading States (skeleton screens, spinners, optimistic UI), Error States, Success States, Confirmation Dialogs, Onboarding Flows, and Progressive Disclosure. These patterns apply to every feature — done wrong, they're the biggest source of user confusion.

typescript-patterns

8
from marvinrichter/clarc

TypeScript patterns — type system best practices, strict mode, utility types, generics, discriminated unions, error handling with Result types, and module organization. Core patterns for production TypeScript.

typescript-patterns-advanced

8
from marvinrichter/clarc

Advanced TypeScript — mapped types, template literal types, conditional types, infer, type guards, decorators, async patterns, testing with Vitest/Jest, and performance. Extends typescript-patterns.

typescript-monorepo-patterns

8
from marvinrichter/clarc

TypeScript monorepo patterns with Turborepo + pnpm workspaces. Covers package structure, shared configs, task pipeline caching, build orchestration, and publishing strategy.

terraform-ci

8
from marvinrichter/clarc

Terraform in CI/CD — plan on PR, apply on merge, OIDC auth, drift detection, importing existing resources, common CLI commands, anti-patterns, and Terraform vs Pulumi vs CDK decision guide.

swiftui-patterns

8
from marvinrichter/clarc

SwiftUI architecture patterns, state management with @Observable, view composition, navigation, performance optimization, and modern iOS/macOS UI best practices.

swift-patterns

8
from marvinrichter/clarc

Core Swift patterns — value vs reference types, protocols, generics, optionals, Result, error handling, Codable, and module organization. Foundation for all Swift development.

swift-patterns-advanced

8
from marvinrichter/clarc

Advanced Swift patterns — property wrappers, result builders, Combine basics, opaque & existential types, macro system, advanced generics, and performance optimization. Extends swift-patterns.