Terraform Design Skill
When to activate
When designing new Terraform projects, refactoring module structure, establishing variable taxonomy, planning remote state configuration, or architecting multi-environment infrastructure with Terraform.
When NOT to use
For one-off infrastructure scripts or temporary testing environments that don't require modularity, versioning, or team collaboration.
Instructions
Design Phase
Start with infrastructure requirements and decompose into modules:
Identify module boundaries: Groups of resources that are logically cohesive, reusable, and independently managed
- Core infrastructure: VPC, subnets, route tables, NAT gateways
- Compute: ECS clusters, Kubernetes worker nodes, Lambda layers
- Databases: RDS instances, DynamoDB tables, Elasticache clusters
- Security: IAM roles, policies, KMS keys, security groups
- Observability: CloudWatch, Prometheus, ELK stack
Design variable taxonomy:
- Required inputs: CIDR blocks, instance types, database sizes
- Optional inputs with sensible defaults: environment tags, replica counts
- Use
type constraints: string, number, list(string), object({...})
- Add
validation blocks for non-obvious constraints
- Document with
description and sensitive flags
Design outputs:
- Export only what consumers need: VPC ID, security group IDs, database endpoints
- Use descriptive names:
vpc_id, private_subnet_ids, rds_endpoint
- Add
description to every output
Plan state management:
- Local state only for dev/local testing
- Remote state for all production infrastructure:
- Backend: S3 + DynamoDB for AWS, Cloud Storage + Firestore for GCP, Storage Account for Azure
- Encryption at rest and in transit
- State locking to prevent concurrent modifications
- Separate state files per environment: dev, staging, prod
- Never commit state files; use
.gitignore
Design directory structure:
terraform/
├── modules/ # Reusable modules
│ ├── vpc/
│ ├── rds/
│ ├── security_group/
│ └── iam/
├── environments/ # Environment-specific configs
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── prod/
├── global/ # Shared infrastructure
│ ├── main.tf
│ └── variables.tf
├── main.tf # Root module
├── variables.tf
├── outputs.tf
├── versions.tf
├── terraform.tfvars # Shared variables (non-sensitive)
└── README.md
Module Implementation
# modules/vpc/main.tf
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
variable "project_name" {
type = string
description = "Project name for resource naming"
}
variable "vpc_cidr" {
type = string
description = "CIDR block for VPC"
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "vpc_cidr must be a valid CIDR block"
}
}
variable "enable_nat_gateway" {
type = bool
default = true
description = "Enable NAT Gateway for private subnet egress"
}
variable "tags" {
type = map(string)
default = {}
description = "Common tags for all resources"
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(
var.tags,
{
Name = "${var.project_name}-vpc"
}
)
}
resource "aws_subnet" "private" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 2, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = merge(
var.tags,
{
Name = "${var.project_name}-private-subnet-${count.index + 1}"
Tier = "Private"
}
)
}
output "vpc_id" {
value = aws_vpc.main.id
description = "VPC ID"
}
output "vpc_cidr_block" {
value = aws_vpc.main.cidr_block
description = "VPC CIDR block"
}
output "private_subnet_ids" {
value = aws_subnet.private[*].id
description = "Private subnet IDs"
}
Remote State Configuration
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Environment Variables
# environments/prod/variables.tfvars
project_name = "myapp"
vpc_cidr = "10.0.0.0/16"
enable_nat_gateway = true
tags = {
environment = "prod"
owner = "platform-team"
cost_center = "engineering"
}
Example
Scenario: Design Terraform for a microservices platform with VPC, EKS cluster, RDS database, and observability stack.
Solution:
Module structure:
modules/vpc/ — VPC, subnets, NAT, route tables
modules/eks/ — EKS cluster, worker nodes, node groups
modules/rds/ — RDS instance, subnet group, parameter group
modules/kms/ — Encryption keys
modules/monitoring/ — CloudWatch, application logs
Variables taxonomy:
- Required:
environment, vpc_cidr, cluster_version, db_instance_class
- Optional:
enable_nat_gateway=true, enable_monitoring=true, replica_count=3
- All inputs validated: CIDR format, EKS version compatibility, instance type whitelist
Outputs:
eks_cluster_name — For kubectl config
rds_endpoint — For application config
kms_key_arn — For encryption policies
nat_gateway_ips — For firewall rules
State strategy:
- Prod state in S3 with encryption, versioning, locking
- Separate state files:
prod/kubernetes.tfstate, prod/databases.tfstate
- State locked during terraform apply
Directory layout:
terraform/
├── modules/
│ ├── vpc/
│ ├── eks/
│ ├── rds/
│ ├── kms/
│ └── monitoring/
├── environments/prod/
│ ├── main.tf # Root module composition
│ ├── variables.tfvars
│ ├── backend.tf
│ └── outputs.tf
└── versions.tf # Provider versions (shared)
This design ensures:
- Modules are reusable across environments
- Variables parameterize all infrastructure
- State is safe and versioned
- Directory structure scales to 100+ resources
- New team members understand the layout
1---2name: terraform-design3description: Terraform Design Skill4---5# Terraform Design Skill67## When to activate89When designing new Terraform projects, refactoring module structure, establishing variable taxonomy, planning remote state configuration, or architecting multi-environment infrastructure with Terraform.1011## When NOT to use1213For one-off infrastructure scripts or temporary testing environments that don't require modularity, versioning, or team collaboration.1415## Instructions1617### Design Phase1819Start with infrastructure requirements and decompose into modules:20211. **Identify module boundaries:** Groups of resources that are logically cohesive, reusable, and independently managed22 - Core infrastructure: VPC, subnets, route tables, NAT gateways23 - Compute: ECS clusters, Kubernetes worker nodes, Lambda layers24 - Databases: RDS instances, DynamoDB tables, Elasticache clusters25 - Security: IAM roles, policies, KMS keys, security groups26 - Observability: CloudWatch, Prometheus, ELK stack27282. **Design variable taxonomy:**29 - Required inputs: CIDR blocks, instance types, database sizes30 - Optional inputs with sensible defaults: environment tags, replica counts31 - Use `type` constraints: `string`, `number`, `list(string)`, `object({...})`32 - Add `validation` blocks for non-obvious constraints33 - Document with `description` and `sensitive` flags34353. **Design outputs:**36 - Export only what consumers need: VPC ID, security group IDs, database endpoints37 - Use descriptive names: `vpc_id`, `private_subnet_ids`, `rds_endpoint`38 - Add `description` to every output39404. **Plan state management:**41 - Local state only for dev/local testing42 - Remote state for all production infrastructure:43 - Backend: S3 + DynamoDB for AWS, Cloud Storage + Firestore for GCP, Storage Account for Azure44 - Encryption at rest and in transit45 - State locking to prevent concurrent modifications46 - Separate state files per environment: dev, staging, prod47 - Never commit state files; use `.gitignore`48495. **Design directory structure:**50 ```51 terraform/52 ├── modules/ # Reusable modules53 │ ├── vpc/54 │ ├── rds/55 │ ├── security_group/56 │ └── iam/57 ├── environments/ # Environment-specific configs58 │ ├── dev/59 │ │ ├── main.tf60 │ │ ├── variables.tfvars61 │ │ └── backend.tf62 │ ├── staging/63 │ └── prod/64 ├── global/ # Shared infrastructure65 │ ├── main.tf66 │ └── variables.tf67 ├── main.tf # Root module68 ├── variables.tf69 ├── outputs.tf70 ├── versions.tf71 ├── terraform.tfvars # Shared variables (non-sensitive)72 └── README.md73 ```7475### Module Implementation7677```hcl78# modules/vpc/main.tf79terraform {80 required_version = ">= 1.5"81 required_providers {82 aws = {83 source = "hashicorp/aws"84 version = "~> 5.0"85 }86 }87}8889variable "project_name" {90 type = string91 description = "Project name for resource naming"92}9394variable "vpc_cidr" {95 type = string96 description = "CIDR block for VPC"97 validation {98 condition = can(cidrhost(var.vpc_cidr, 0))99 error_message = "vpc_cidr must be a valid CIDR block"100 }101}102103variable "enable_nat_gateway" {104 type = bool105 default = true106 description = "Enable NAT Gateway for private subnet egress"107}108109variable "tags" {110 type = map(string)111 default = {}112 description = "Common tags for all resources"113}114115resource "aws_vpc" "main" {116 cidr_block = var.vpc_cidr117 enable_dns_hostnames = true118 enable_dns_support = true119120 tags = merge(121 var.tags,122 {123 Name = "${var.project_name}-vpc"124 }125 )126}127128resource "aws_subnet" "private" {129 count = 3130 vpc_id = aws_vpc.main.id131 cidr_block = cidrsubnet(var.vpc_cidr, 2, count.index)132 availability_zone = data.aws_availability_zones.available.names[count.index]133134 tags = merge(135 var.tags,136 {137 Name = "${var.project_name}-private-subnet-${count.index + 1}"138 Tier = "Private"139 }140 )141}142143output "vpc_id" {144 value = aws_vpc.main.id145 description = "VPC ID"146}147148output "vpc_cidr_block" {149 value = aws_vpc.main.cidr_block150 description = "VPC CIDR block"151}152153output "private_subnet_ids" {154 value = aws_subnet.private[*].id155 description = "Private subnet IDs"156}157```158159### Remote State Configuration160161```hcl162# backend.tf163terraform {164 backend "s3" {165 bucket = "my-terraform-state"166 key = "prod/terraform.tfstate"167 region = "us-east-1"168 encrypt = true169 dynamodb_table = "terraform-locks"170 }171}172```173174### Environment Variables175176```hcl177# environments/prod/variables.tfvars178project_name = "myapp"179vpc_cidr = "10.0.0.0/16"180enable_nat_gateway = true181tags = {182 environment = "prod"183 owner = "platform-team"184 cost_center = "engineering"185}186```187188## Example189190**Scenario:** Design Terraform for a microservices platform with VPC, EKS cluster, RDS database, and observability stack.191192**Solution:**1931941. **Module structure:**195 - `modules/vpc/` — VPC, subnets, NAT, route tables196 - `modules/eks/` — EKS cluster, worker nodes, node groups197 - `modules/rds/` — RDS instance, subnet group, parameter group198 - `modules/kms/` — Encryption keys199 - `modules/monitoring/` — CloudWatch, application logs2002012. **Variables taxonomy:**202 - Required: `environment`, `vpc_cidr`, `cluster_version`, `db_instance_class`203 - Optional: `enable_nat_gateway=true`, `enable_monitoring=true`, `replica_count=3`204 - All inputs validated: CIDR format, EKS version compatibility, instance type whitelist2052063. **Outputs:**207 - `eks_cluster_name` — For kubectl config208 - `rds_endpoint` — For application config209 - `kms_key_arn` — For encryption policies210 - `nat_gateway_ips` — For firewall rules2112124. **State strategy:**213 - Prod state in S3 with encryption, versioning, locking214 - Separate state files: `prod/kubernetes.tfstate`, `prod/databases.tfstate`215 - State locked during terraform apply2162175. **Directory layout:**218 ```219 terraform/220 ├── modules/221 │ ├── vpc/222 │ ├── eks/223 │ ├── rds/224 │ ├── kms/225 │ └── monitoring/226 ├── environments/prod/227 │ ├── main.tf # Root module composition228 │ ├── variables.tfvars229 │ ├── backend.tf230 │ └── outputs.tf231 └── versions.tf # Provider versions (shared)232 ```233234This design ensures:235- Modules are reusable across environments236- Variables parameterize all infrastructure237- State is safe and versioned238- Directory structure scales to 100+ resources239- New team members understand the layout