Infrastructure as Code: Terraform Patterns That Scale
Organizing Terraform code for real-world projects. Modules, workspaces, state management, and team collaboration patterns.
Beyond the Basics
Everyone can write basic Terraform. The challenge is organizing Terraform for teams, multiple environments, and growing infrastructure. Here are the patterns I've found work at scale.
Module Design
Good modules are the foundation of maintainable Terraform. A module should do one thing well and be reusable across environments.
# modules/vpc/main.tf
variable "environment" {
type = string
description = "Environment name (staging, production)"
}
variable "cidr_block" {
type = string
description = "VPC CIDR block"
}
variable "availability_zones" {
type = list(string)
description = "AZs to use for subnets"
}
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
ManagedBy = "terraform"
}
}
# ... subnet creation, route tables, etc.
output "vpc_id" {
value = aws_vpc.main.id
}Environment Separation
Keep environments isolated. I use separate state files and separate directories for each environment:
terraform/
├── modules/
│ ├── vpc/
│ ├── eks/
│ └── rds/
├── environments/
│ ├── staging/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── production/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
└── backend.tfState Management
Remote state with locking is non-negotiable for teams. I use S3 + DynamoDB for AWS projects:
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "environments/production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}Never store state files in Git. They can contain secrets and will cause conflicts when multiple people work on infrastructure.
CI/CD Integration
Terraform changes should go through the same review process as application code. Here's my GitHub Actions workflow:
name: Terraform
on:
pull_request:
paths: ['terraform/**']
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Terraform Init
run: terraform init
working-directory: terraform/environments/staging
- name: Terraform Plan
run: terraform plan -out=plan.tfplan
working-directory: terraform/environments/staging
- name: Comment Plan
uses: actions/github-script@v7
with:
script: |
// Post plan output as PR commentFound this helpful?
I write about infrastructure, backend development, and DevOps. Follow along as I continue building.