Module composition, state management, workspace strategy, provider versioning, and infrastructure-as-code best practices.
Scanned 5/29/2026
npx -y skills add vibeeval/vibecosystem --skill terraform-patterns --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Terraform Patterns?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/vibeeval-terraform-patterns)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: terraform-patterns
description: Module composition, state management, workspace strategy, provider versioning, and infrastructure-as-code best practices.
---
# Terraform Patterns
Production Terraform patterns for maintainable infrastructure-as-code.
## Module Composition
```hcl
# modules/vpc/main.tf - Reusable VPC module
variable "name" {
type = string
description = "VPC name prefix"
}
variable "cidr" {
type = string
default = "10.0.0.0/16"
}
variable "azs" {
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
resource "aws_vpc" "main" {
cidr_block = var.cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = var.name
ManagedBy = "terraform"
Environment = terraform.workspace
}
}
resource "aws_subnet" "private" {
count = length(var.azs)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr, 8, count.index)
availability_zone = var.azs[count.index]
tags = {
Name = "${var.name}-private-${var.azs[count.index]}"
Tier = "private"
}
}
output "vpc_id" {
value = aws_vpc.main.id
}
output "private_subnet_ids" {
value = aws_subnet.private[*.id]
}
```
## Root Module Composition
```hcl
# environments/production/main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Pin major version, allow minor updates
}
}
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks" # State locking
encrypt = true
}
}
module "vpc" {
source = "../../modules/vpc"
name = "prod"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
module "database" {
source = "../../modules/rds"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
instance_class = "db.r6g.xlarge"
allocated_storage = 100
multi_az = true
}
module "app" {
source = "../../modules/ecs-service"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
container_image = "myapp:${var.app_version}"
desired_count = 3
cpu = 512
memory = 1024
environment_variables = {
DATABASE_URL = module.database.connection_string
LOG_LEVEL = "info"
}
}
```
## State Management
```hcl
# Remote state data source: reference other state files safely
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "mycompany-terraform-state"
key = "networking/terraform.tfstate"
region = "us-east-1"
}
}
# Use outputs from networking state
module "app" {
source = "../../modules/ecs-service"
vpc_id = data.terraform_remote_state.networking.outputs.vpc_id
subnet_ids = data.terraform_remote_state.networking.outputs.private_subnet_ids
}
```
## Resource Lifecycle
```hcl
resource "aws_instance" "app" {
ami = var.ami_id
instance_type = var.instance_type
lifecycle {
# Create new before destroying old (zero-downtime replacement)
create_before_destroy = true
# Prevent accidental deletion of critical resources
prevent_destroy = true
# Ignore changes made outside Terraform (e.g., auto-scaling tags)
ignore_changes = [tags["LastScaleEvent"]]
}
}
# Import existing resources
import {
to = aws_s3_bucket.existing
id = "my-existing-bucket-name"
}
```
## Variables and Validation
```hcl
variable "environment" {
type = string
description = "Deployment environment"
validation {
condition = contains(["development", "staging", "production"], var.environment)
error_message = "Environment must be development, staging, or production."
}
}
variable "instance_count" {
type = number
default = 2
validation {
condition = var.instance_count >= 1 && var.instance_count <= 20
error_message = "Instance count must be between 1 and 20."
}
}
# Local values for computed configuration
locals {
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "terraform"
Team = var.team
}
is_production = var.environment == "production"
replica_count = local.is_production ? 3 : 1
}
```
## Dynamic Blocks and for_each
```hcl
# for_each over map (preferred over count for named resources)
variable "services" {
type = map(object({
port = number
health_path = string
replicas = number
}))
}
resource "aws_ecs_service" "services" {
for_each = var.services
name = each.key
cluster = aws_ecs_cluster.main.id
desired_count = each.value.replicas
task_definition = aws_ecs_task_definition.tasks[each.key].arn
}
# Dynamic block for security group rules
resource "aws_security_group" "app" {
name = "${var.name}-sg"
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ingress.value.cidr_blocks
description = ingress.value.description
}
}
}
```
## Checklist
- [ ] Pin provider versions with `~>` (allow patch, lock major/minor)
- [ ] Remote state with locking (S3 + DynamoDB or Terraform Cloud)
- [ ] Separate state files per environment (not workspaces for prod vs dev)
- [ ] `prevent_destroy` on databases, S3 buckets, and IAM roles
- [ ] Variable validation blocks for all user-facing inputs
- [ ] Common tags via `locals` applied to every resource
- [ ] Use `for_each` over `count` (survives reordering without recreation)
- [ ] Run `terraform plan` in CI, `terraform apply` requires approval
## Anti-Patterns
- Single state file for all environments: one bad apply affects everything
- Hardcoded values: use variables with defaults and validation
- Using `count` with lists: removing item N recreates items N+1 through end
- No state locking: concurrent applies corrupt state
- Monolithic root module: 500+ resources in one state (split by lifecycle)
- Storing secrets in `.tf` files: use AWS Secrets Manager or Vault references
- Not pinning provider versions: surprise breaking changes on next init
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!