Terraform Patterns Skill
Terraform Patterns Skill
When to Use This Skill
Use this skill when implementing terraform patterns patterns in your codebase.
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
Infrastructure as Code best practices with Terraform including module design, state management, multi-environment configurations, and security hardening.
Project Structure
Recommended Layout
infrastructure/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── compute/
│ └── database/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── production/
├── shared/
│ ├── versions.tf
│ └── providers.tf
└── .terraform-version
Module Design
Reusable Module Template
# modules/service/main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
variable "name" {
description = "Service name"
type = string
validation {
condition = can(regex("^[a-z][a-z0-9-]*$", var.name))
error_message = "Name must be lowercase alphanumeric with hyphens."
}
}
variable "environment" {
description = "Environment (dev, staging, production)"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
locals {
common_tags = merge(var.tags, {
Service = var.name
Environment = var.environment
ManagedBy = "terraform"
})
}
output "service_id" {
description = "Service identifier"
value = aws_ecs_service.main.id
}
Module Composition
# environments/production/main.tf
module "vpc" {
source = "../../modules/networking"
name = "main"
environment = "production"
cidr_block = "10.0.0.0/16"
}
module "database" {
source = "../../modules/database"
name = "api"
environment = "production"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
depends_on = [module.vpc]
}
module "service" {
source = "../../modules/service"
name = "api"
environment = "production"
vpc_id = module.vpc.vpc_id
database_url = module.database.connection_string
}
State Management
Remote State Configuration
# backend.tf
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "production/infrastructure.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
State Data Sources
# Reference another state
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "company-terraform-state"
key = "production/networking.tfstate"
region = "us-east-1"
}
}
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_id
}
State Import
# Import existing resources
terraform import aws_instance.web i-1234567890abcdef0
# Generate configuration from import
terraform plan -generate-config-out=generated.tf
Multi-Environment Patterns
Terragrunt DRY Configuration
# terragrunt.hcl (root)
remote_state {
backend = "s3"
config = {
bucket = "company-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
inputs = {
region = "us-east-1"
}
# environments/production/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../modules//service"
}
inputs = {
environment = "production"
instance_count = 3
instance_type = "t3.large"
}
Workspaces Pattern
# Create and switch workspaces
terraform workspace new production
terraform workspace select production
# Apply per workspace
terraform apply -var-file="environments/${TF_WORKSPACE}.tfvars"
# Use workspace in configuration
locals {
environment = terraform.workspace
instance_counts = {
dev = 1
staging = 2
production = 3
}
}
resource "aws_instance" "app" {
count = local.instance_counts[local.environment]
instance_type = local.environment == "production" ? "t3.large" : "t3.small"
}
Security Best Practices
Sensitive Variables
variable "database_password" {
description = "Database password"
type = string
sensitive = true
}
output "connection_string" {
value = "postgres://user:${var.database_password}@${aws_db_instance.main.endpoint}/db"
sensitive = true
}
AWS Secrets Manager Integration
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "production/database/password"
}
resource "aws_db_instance" "main" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
IAM Least Privilege
data "aws_iam_policy_document" "lambda" {
statement {
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
resources = ["arn:aws:logs:*:*:*"]
}
statement {
effect = "Allow"
actions = [
"s3:GetObject"
]
resources = ["${aws_s3_bucket.data.arn}/*"]
}
}
Security Scanning
# tflint - Terraform linter
tflint --init
tflint --recursive
# checkov - Security scanner
checkov -d .
# tfsec - Security scanner
tfsec .
Cloud Provider Patterns
AWS ECS Fargate
resource "aws_ecs_service" "api" {
name = "api"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.api.arn
desired_count = 3
launch_type = "FARGATE"
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.api.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.api.arn
container_name = "api"
container_port = 8080
}
deployment_circuit_breaker {
enable = true
rollback = true
}
}
GCP Cloud Run
resource "google_cloud_run_service" "api" {
name = "api"
location = var.region
template {
spec {
containers {
image = "gcr.io/${var.project_id}/api:${var.image_tag}"
ports {
container_port = 8080
}
resources {
limits = {
cpu = "1000m"
memory = "512Mi"
}
}
}
}
metadata {
annotations = {
"autoscaling.knative.dev/maxScale" = "10"
}
}
}
traffic {
percent = 100
latest_revision = true
}
}
Kubernetes Provider
provider "kubernetes" {
host = data.aws_eks_cluster.main.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.main.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.main.token
}
resource "kubernetes_deployment" "api" {
metadata {
name = "api"
namespace = "default"
}
spec {
replicas = 3
selector {
match_labels = {
app = "api"
}
}
template {
metadata {
labels = {
app = "api"
}
}
spec {
container {
name = "api"
image = "api:latest"
port {
container_port = 8080
}
resources {
limits = {
cpu = "500m"
memory = "256Mi"
}
requests = {
cpu = "250m"
memory = "128Mi"
}
}
}
}
}
}
}
Testing and Validation
Terraform Validate
terraform init -backend=false
terraform validate
terraform fmt -check -recursive
Terratest Example
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/networking",
Vars: map[string]interface{}{
"name": "test",
"environment": "dev",
"cidr_block": "10.0.0.0/16",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)
}
Plan Validation
# Generate plan
terraform plan -out=tfplan
# Convert to JSON for analysis
terraform show -json tfplan > plan.json
# Check for resource deletions
jq '.resource_changes[] | select(.change.actions | contains(["delete"]))' plan.json
CI/CD Integration
GitHub Actions
name: Terraform
on:
pull_request:
paths:
- 'infrastructure/**'
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.0
- run: terraform init
working-directory: infrastructure/environments/production
- run: terraform plan -no-color
working-directory: infrastructure/environments/production
continue-on-error: true
- uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '```\n' + process.env.PLAN_OUTPUT + '\n```'
})
Troubleshooting
State Locking Issues
# Force unlock (use carefully)
terraform force-unlock LOCK_ID
# Debug state
terraform state list
terraform state show aws_instance.web
Provider Issues
# Clear provider cache
rm -rf .terraform
# Upgrade providers
terraform init -upgrade
Usage Examples
Design Infrastructure Module
Apply terraform-patterns skill to design reusable VPC module with public/private subnets, NAT gateways, and security groups
Multi-Environment Setup
Apply terraform-patterns skill to configure Terragrunt for dev/staging/production environments with DRY configuration
Success Output
When successful, this skill MUST output:
✅ SKILL COMPLETE: terraform-patterns
Completed:
- [x] Terraform modules created with proper structure (main.tf, variables.tf, outputs.tf)
- [x] Remote state backend configured (S3 + DynamoDB or equivalent)
- [x] Multi-environment configuration implemented (dev/staging/production)
- [x] Security best practices applied (sensitive variables, least privilege IAM)
- [x] terraform validate passes without errors
- [x] terraform plan reviewed and approved
- [x] State locking verified
Outputs:
- modules/*/ directories with reusable modules
- environments/*/ directories with environment-specific configs
- backend.tf with remote state configuration
- terraform.tfvars.example for variable reference
- .terraform.lock.hcl with provider version locks
Completion Checklist
Before marking this skill as complete, verify:
-
terraform initcompletes without errors -
terraform validatepasses -
terraform fmt -check -recursivepasses (code formatted) -
terraform planshows expected changes only - All sensitive variables marked with
sensitive = true - IAM policies follow least privilege principle
- State backend configured with encryption and locking
- Provider versions pinned in required_providers block
- Module README.md documents inputs, outputs, and usage
- Security scanner (tfsec, checkov) passes with no critical issues
Failure Indicators
This skill has FAILED if:
- ❌
terraform validatefails with syntax or reference errors - ❌ No remote state backend configured (local state in production)
- ❌ Hardcoded secrets in .tf files
- ❌ IAM policies grant
*permissions - ❌ No state locking (DynamoDB table missing for S3 backend)
- ❌ Provider versions not pinned (using unstable versions)
- ❌ terraform plan shows unintended resource deletions
- ❌ Security scanner reports critical vulnerabilities
- ❌ No .gitignore for .tfstate, .terraform/, *.tfvars
When NOT to Use
Do NOT use this skill when:
- Using CloudFormation, Pulumi, or other IaC tools (use tool-specific patterns)
- Managing Kubernetes manifests (use Helm or Kustomize)
- Provisioning serverless applications (use SAM or Serverless Framework)
- One-time manual infrastructure setup (Terraform overkill for single resources)
- Infrastructure requires extreme customization (write custom scripts)
- Team lacks Terraform expertise (consider managed services first)
- Quick prototyping without state management requirements
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Hardcoded values | Not reusable across environments | Use variables with defaults |
| No remote state | State conflicts, no collaboration | Configure S3 + DynamoDB backend |
| Monolithic configuration | Hard to manage, slow plans | Split into modules and environments |
| Wildcard provider versions | Breaking changes on updates | Pin versions with ~> 5.0 |
| No state locking | Concurrent runs corrupt state | Enable DynamoDB locking table |
| Inline IAM policies | Hard to audit and reuse | Use aws_iam_policy_document data sources |
| terraform apply without plan | Dangerous, unreviewed changes | Always run plan first, review, then apply |
| No .gitignore | Secrets leaked in state files | Ignore .tfstate, *.tfvars, .terraform/ |
Principles
This skill embodies:
- #1 Recycle → Extend → Re-Use → Create - Reusable modules over copy-paste
- #2 First Principles - Infrastructure as Code fundamentals
- #3 Keep It Simple - DRY configuration with Terragrunt or workspaces
- #4 Separation of Concerns - Modules for logical infrastructure components
- #5 Eliminate Ambiguity - Explicit variable types and validation
- #8 No Assumptions - Always run terraform plan before apply
- #13 Automation - Terraform enables infrastructure automation
Full Principles: CODITECT-STANDARD-AUTOMATION.md