Terraform Infrastructure-as-Code
Enterprise Terraform standards for our AWS stack: ECS Fargate, RDS PostgreSQL+PostGIS, ElastiCache Redis, S3, CloudFront, Centrifugo, with directory-based environment isolation and remote state.
When to Apply
Reference these guidelines when:
- Writing or reviewing Terraform configurations
- Creating or updating infrastructure modules
- Managing Terraform state (remote backends, imports, moves)
- Designing networking, security groups, or IAM policies
- Provisioning data stores (RDS, ElastiCache, S3)
- Setting up compute resources (ECS Fargate, Lambda)
- Reviewing infrastructure PRs for security and cost
Quick Decision Matrix
| Decision | Rule | Action |
|---|---|---|
| Where to store state? | state-remote-backend |
S3 + DynamoDB, always |
| Multiple environments? | state-workspace-isolation |
Directory-based, never workspaces |
| Existing AWS resource? | state-import-before-adopt |
terraform import first |
| Renaming a module? | state-move-not-destroy |
moved block or state mv |
| Secrets in HCL? | sec-no-hardcoded-secrets |
Never. Use Secrets Manager + sensitive vars |
| IAM policy scope? | sec-iam-least-privilege |
Specific actions + resources, never * |
| Security group ingress? | sec-security-group-restrict |
No 0.0.0.0/0 except ALB 80/443 |
| Provider versions? | sec-provider-version-constraint |
Pin with ~> constraints |
| Module structure? | module-single-responsibility |
One concern per module |
| Resource naming? | resource-naming-convention |
{project}-{env}-{service}-{resource} |
| Tags? | resource-required-tags |
project, environment, team, managed-by |
count vs for_each? |
resource-for-each-over-count |
Always for_each for named resources |
| Variable types? | var-type-constraints |
Explicit type on every variable |
| Subnet design? | net-multi-az-subnets |
Public/private/data across 2+ AZs |
| RDS configuration? | data-rds-postgresql-postgis |
Multi-AZ prod, PostGIS, custom params |
| ECS task sizing? | compute-ecs-fargate-task-definition |
Right-size CPU/memory, use secrets block |
| Dev cost savings? | cost-dev-environment-scheduling |
Scale to zero off-hours |
Rule Categories
| # | Category | Prefix | Priority | Rules | Description |
|---|---|---|---|---|---|
| 1 | State Management | state- |
CRITICAL | 6 | Remote backends, locking, workspace isolation, imports, moves, sensitive outputs |
| 2 | Security | sec- |
CRITICAL | 7 | Secrets, IAM, encryption, security groups, provider pinning, VPC isolation |
| 3 | Module Design | module- |
HIGH | 6 | Module structure, inputs/outputs, versioning, composition, documentation |
| 4 | Resource Patterns | resource- |
HIGH | 6 | Naming, tagging, lifecycle, dependencies, data sources, conditional creation |
| 5 | Variables & Outputs | var- |
MEDIUM-HIGH | 5 | Typing, validation, defaults, descriptions, output structure |
| 6 | Networking | net- |
MEDIUM | 5 | VPC design, subnet tiers, NAT, DNS, transit gateway |
| 7 | Data Stores | data- |
MEDIUM | 5 | RDS, ElastiCache, S3, backups, parameter groups |
| 8 | Compute | compute- |
MEDIUM | 4 | ECS Fargate, task definitions, autoscaling, health checks |
| 9 | Cost Optimization | cost- |
LOW-MEDIUM | 3 | Right-sizing, reserved capacity, unused resource cleanup |
Quick Reference -- All 47 Rules
State Management (CRITICAL)
state-import-before-adopt-- Import Existing Resources Before Managingstate-lock-configuration-- Configure State Lockingstate-move-not-destroy-- Use State Move for Refactoringstate-remote-backend-- Always Use Remote State Backendstate-sensitive-outputs-- Mark Sensitive Outputsstate-workspace-isolation-- Directory-Based Environment Isolation
Security (CRITICAL)
sec-encryption-at-rest-- Encryption at Rest for All Data Storessec-encryption-in-transit-- Encryption in Transitsec-iam-least-privilege-- IAM Least Privilegesec-no-default-vpc-- Never Use Default VPCsec-no-hardcoded-secrets-- No Hardcoded Secretssec-provider-version-constraint-- Pin Provider Version Constraintssec-security-group-restrict-- Restrict Security Group Ingress
Module Design (HIGH)
module-composition-over-monolith-- Module Composition Over Monolithmodule-input-output-contract-- Module Input/Output Contractmodule-output-minimal-- Module Output Minimalmodule-readme-documentation-- Module README Documentationmodule-single-responsibility-- Module Single Responsibilitymodule-version-pinning-- Module Version Pinning
Resource Patterns (HIGH)
resource-data-source-lookup-- Resource Data Source Lookupresource-depends-on-sparingly-- Use depends_on Sparinglyresource-for-each-over-count-- Use for_each Over countresource-lifecycle-rules-- Resource Lifecycle Rulesresource-naming-convention-- Resource Naming Conventionresource-required-tags-- Resource Required Tags
Variables & Outputs (MEDIUM-HIGH)
var-descriptive-defaults-- Descriptive Variables with Intentional Defaultsvar-sensitive-marking-- Mark Sensitive Variablesvar-tfvars-per-environment-- Separate tfvars per Environmentvar-type-constraints-- Explicit Type Constraints on Variablesvar-validation-blocks-- Validation Blocks on Variables
Networking (MEDIUM)
net-alb-configuration-- ALB Configuration with HTTPS and Health Checksnet-multi-az-subnets-- Multi-AZ Subnet Tiersnet-nat-gateway-ha-- NAT Gateway High Availabilitynet-private-subnets-for-data-- Private Subnets for Data Servicesnet-vpc-cidr-planning-- VPC CIDR Block Planning
Data Stores (MEDIUM)
data-backup-retention-- Backup Retention Policiesdata-connection-strings-via-ssm-- Connection Strings via Secrets Managerdata-elasticache-redis-- ElastiCache Redis with Failoverdata-rds-postgresql-postgis-- RDS PostgreSQL with PostGIS Extensiondata-s3-bucket-policy-- S3 Bucket Security and Lifecycle
Compute (MEDIUM)
compute-ecr-lifecycle-policy-- ECR Lifecycle Policycompute-ecs-deployment-configuration-- ECS Deployment Configurationcompute-ecs-fargate-task-definition-- ECS Fargate Task Definitioncompute-ecs-service-autoscaling-- ECS Service Auto Scaling
Cost Optimization (LOW-MEDIUM)
cost-dev-environment-scheduling-- Dev Environment Scheduling and Scale-to-Zerocost-right-sizing-- Right-Size Resources per Environmentcost-s3-intelligent-tiering-- S3 Lifecycle Rules and Intelligent Tiering
Deep guides (read on demand, do not preload)
Every rule id listed above maps to a self-contained file at rules/<rule-id>.md, each with its
own bad/good code pair.
Read only the rule file matching your task — not the set:
rules/<rule-id>.md
Loading one ~60-line rule beats skimming a compiled monolith. Do not preload the directory.
Two references cover what spans rules rather than living inside any one of them. Read either only when the task calls for it:
| Reference | Read it when |
|---|---|
references/repository-layout.md |
Laying out a new Terraform repo, deciding which file a block belongs in, writing versions.tf/locals.tf, or choosing between per-environment root modules and a single root with -var-file |
references/enforcement-and-tooling.md |
A hook rejected your edit, a terraform apply was gated, or you need to know what the automated checks do not catch |
Owned elsewhere
std-terraform-conventionsis the Terraform rule set: it is scoped to every**/*.tfand**/*.tfvars, any wrapper directory. File structure, provider constraints, resource naming,std-terraform-conventionsis the house rule set, path-scoped to**/*.tfand**/*.tfvars, any wrapper directory. File structure, provider constraints, resource naming, the required tag set, backend config, security minimums. Read it before you write.tf— the scoping gates when it may load, it does not put it in front of you. What does fire regardless isterraform-checker.pyon every.tfedit, andterraform-command-gate.pyon the commands. Read it too when you are planning infrastructure that does not exist yet, since no glob matches a file you have not created.
@skills/std-infrastructure/references/terraform-mechanics.md— state, variables, tagging and secrets as decisions rather than rules. It overlapsreferences/repository-layout.mdon two questions (where a.tffile goes; where tags come from) and agrees with it; read either.rules/resource-required-tags.mdis authoritative for the tag set itself.terraform-checker.pywarns on.tfedits (hardcoded secrets, snake_case naming, the four required tags, backend config, provider versions);terraform-command-gate.pyis a three-tier gate on the commands. The tag list the hook enforces and the tag list these skills document are held in sync by a test — if they ever disagree, that is a bug in the plugin, not in your code.