--- name: terraform-patterns description: Terraform infrastructure as code patterns and best practices. Use when writing Terraform configurations, creating modules, managing state, or designing IaC architectures. --- # Terraform Patterns Best practices for Terraform infrastructure as code. ## Project Structure ``` infrastructure/ ├── modules/ │ ├── networking/ │ │ ├── main.tf │ │ ├── variables.tf │ │ ├── outputs.tf │ │ └── README.md │ ├── compute/ │ └── database/ ├── environments/ │ ├── dev/ │ │ ├── main.tf │ │ ├── terraform.tfvars │ │ └── backend.tf │ ├── staging/ │ └── prod/ ├── global/ │ ├── iam/ │ └── dns/ └── scripts/ └── init-backend.sh ``` ## Module Design ### Reusable Module Pattern ```hcl # modules/vpc/variables.tf variable "name" { description = "Name prefix for resources" type = string } variable "environment" { description = "Environment name" type = string validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "Environment must be dev, staging, or prod." } } variable "cidr_block" { description = "VPC CIDR block" type = string default = "10.0.0.0/16" validation { condition = can(cidrhost(var.cidr_block, 0)) error_message = "Must be a valid CIDR block." } } variable "availability_zones" { description = "List of availability zones" type = list(string) } variable "tags" { description = "Additional tags" type = map(string) default = {} } ``` ```hcl # modules/vpc/main.tf locals { common_tags = merge(var.tags, { Module = "vpc" Environment = var.environment ManagedBy = "terraform" }) } resource "aws_vpc" "main" { cidr_block = var.cidr_block enable_dns_hostnames = true enable_dns_support = true tags = merge(local.common_tags, { Name = "${var.name}-vpc" }) } resource "aws_subnet" "public" { count = length(var.availability_zones) vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.cidr_block, 8, count.index) availability_zone = var.availability_zones[count.index] map_public_ip_on_launch = true tags = merge(local.common_tags, { Name = "${var.name}-public-${count.index + 1}" Tier = "public" }) } resource "aws_subnet" "private" { count = length(var.availability_zones) vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.cidr_block, 8, count.index + 10) availability_zone = var.availability_zones[count.index] tags = merge(local.common_tags, { Name = "${var.name}-private-${count.index + 1}" Tier = "private" }) } ``` ```hcl # modules/vpc/outputs.tf output "vpc_id" { description = "VPC ID" value = aws_vpc.main.id } output "public_subnet_ids" { description = "List of public subnet IDs" value = aws_subnet.public[*].id } output "private_subnet_ids" { description = "List of private subnet IDs" value = aws_subnet.private[*].id } output "vpc_cidr_block" { description = "VPC CIDR block" value = aws_vpc.main.cidr_block } ``` ## State Management ### Remote Backend Configuration ```hcl # backend.tf terraform { backend "s3" { bucket = "company-terraform-state" key = "environments/prod/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-locks" # Assume role for cross-account access role_arn = "arn:aws:iam::ACCOUNT_ID:role/TerraformStateAccess" } } ``` ### State Locking Table ```hcl # global/state-backend/main.tf resource "aws_s3_bucket" "terraform_state" { bucket = "company-terraform-state" lifecycle { prevent_destroy = true } } resource "aws_s3_bucket_versioning" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id versioning_configuration { status = "Enabled" } } resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" } } } resource "aws_dynamodb_table" "terraform_locks" { name = "terraform-locks" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" attribute { name = "LockID" type = "S" } } ``` ## Provider Configuration ### Multi-Region Setup ```hcl provider "aws" { region = var.primary_region default_tags { tags = { Environment = var.environment Project = var.project_name ManagedBy = "terraform" } } } provider "aws" { alias = "dr" region = var.dr_region default_tags { tags = { Environment = var.environment Project = var.project_name ManagedBy = "terraform" } } } ``` ### Version Constraints ```hcl terraform { required_version = ">= 1.5.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } kubernetes = { source = "hashicorp/kubernetes" version = "~> 2.23" } } } ``` ## Data Sources and Lookups ```hcl # Look up existing resources data "aws_vpc" "selected" { filter { name = "tag:Environment" values = [var.environment] } } data "aws_ami" "amazon_linux" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["amzn2-ami-hvm-*-x86_64-gp2"] } } data "aws_caller_identity" "current" {} data "aws_region" "current" {} ``` ## Conditional Resources ```hcl # Create resource only in production resource "aws_cloudwatch_metric_alarm" "high_cpu" { count = var.environment == "prod" ? 1 : 0 alarm_name = "${var.name}-high-cpu" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 metric_name = "CPUUtilization" namespace = "AWS/EC2" period = 300 statistic = "Average" threshold = 80 } # Dynamic blocks for optional configurations resource "aws_security_group" "main" { name = "${var.name}-sg" vpc_id = var.vpc_id dynamic "ingress" { for_each = var.ingress_rules content { from_port = ingress.value.from_port to_port = ingress.value.to_port protocol = ingress.value.protocol cidr_blocks = ingress.value.cidr_blocks } } } ``` ## Lifecycle Rules ```hcl resource "aws_instance" "web" { ami = data.aws_ami.amazon_linux.id instance_type = var.instance_type lifecycle { create_before_destroy = true prevent_destroy = var.environment == "prod" ignore_changes = [ tags["LastModified"], user_data, ] } } ``` ## Testing with Terratest ```go // test/vpc_test.go package test import ( "testing" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" ) func TestVpcModule(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../modules/vpc", Vars: map[string]interface{}{ "name": "test", "environment": "dev", "cidr_block": "10.0.0.0/16", "availability_zones": []string{"us-east-1a", "us-east-1b"}, }, } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) vpcId := terraform.Output(t, terraformOptions, "vpc_id") assert.NotEmpty(t, vpcId) } ``` ## CI/CD Integration ```yaml # .github/workflows/terraform.yml name: Terraform on: pull_request: paths: - 'infrastructure/**' push: branches: [main] paths: - 'infrastructure/**' jobs: plan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: 1.5.0 - name: Terraform Init run: terraform init working-directory: infrastructure/environments/${{ github.event.inputs.environment }} - name: Terraform Plan run: terraform plan -out=tfplan working-directory: infrastructure/environments/${{ github.event.inputs.environment }} - name: Upload Plan uses: actions/upload-artifact@v4 with: name: tfplan path: infrastructure/environments/${{ github.event.inputs.environment }}/tfplan ``` ## References - [Terraform Documentation](https://developer.hashicorp.com/terraform/docs) - [Terraform Best Practices](https://www.terraform-best-practices.com/) - [AWS Provider Documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)