Skip to main content

Terraform standard

Infrastructure is provisioned from version-controlled Terraform, applied through a pipeline, and never changed by hand in the console. This standard defines the repository shape, the module contract and the review gates that make that true in practice.

The standard. A structured, repeatable method for teams to create, manage and update AWS resources through templated provisioning, with deployment, security and governance automated.

Why Terraform​

CriterionTerraformCloudFormation
Multi-cloudProviders for AWS, Azure, GCP and many othersAWS only
ReuseModules with inputs and outputs, versioned independentlyNested stacks and templates
StateExplicit state file with locking, inspectable with terraform stateManaged by CloudFormation
ReadabilityHCL is concise and reviewableJSON/YAML is more verbose
EcosystemLarge provider and module registryAWS-native, no provider layer

Terraform is our default because it lets one configuration model, one review process and one state discipline apply across a customer's whole estate — including the non-AWS services that sit around it. CloudFormation remains appropriate where a customer is AWS-only and already operates it well; the standards below on structure, review and state discipline apply either way.

Repository structure​

.
├── modules/ # Reusable modules, one concern each
│ ├── network/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ └── service/
├── environment/ # One directory per environment
│ ├── dev.tfvars
│ ├── stage.tfvars
│ └── prod.tfvars
├── src/ # Scripts and referenced files
├── docs/
├── backend.tf
├── main.tf
└── .pre-commit-config.yaml

Rules​

  • A module changes one thing. Network, compute, data, IAM — separate modules with explicit inputs and outputs. A module that provisions "everything" cannot be reused or reviewed.
  • Environments differ by variables, not by code. dev.tfvars, stage.tfvars and prod.tfvars set sizes, counts and account targets. The module code is identical across environments.
  • No secrets in variables files. Secrets come from AWS Secrets Manager or Systems Manager Parameter Store at apply time, not from a committed .tfvars.
  • Every module has a README stating its inputs, outputs, dependencies and what it does not manage.
  • Pre-commit hooks are mandatory. Formatting (terraform fmt), validation (terraform validate) and static analysis (Checkov or tfsec) run before a commit is allowed.

Environment separation​

environment/
├── dev.tfvars # smallest viable footprint, disposable
├── stage.tfvars # production-shaped, non-customer traffic
└── prod.tfvars # customer traffic, change-controlled

Changes promote in that order. A change that has not been applied to stage does not go to production, and the pipeline enforces the order rather than relying on discipline.

Each environment has its own state key. Sharing state between environments means a terraform destroy in one environment can affect another.

Pipeline gates​

Format
terraform fmt -check fails the build on unformatted code.
Validate
terraform validate confirms syntax and internal consistency.
Static analysis
Checkov or tfsec scans for insecure configuration — public buckets, open security groups, unencrypted volumes.
Plan
The plan is posted to the pull request. A reviewer approves the plan, not just the code.
Apply
Only from the pipeline, using a short-lived OIDC role. Production applies require a recorded approval.

State​

Remote state in S3 with DynamoDB locking is mandatory for any project with more than one contributor. See Terraform state and the S3 backend standard.

Naming and tagging​

Every resource carries:

TagPurpose
Environmentdev, stage, prod
ProjectThe workload or service name
OwnerThe team accountable for the resource
CostCenterChargeback or showback reference
ManagedByterraform
DataClassificationpublic, internal, confidential, restricted

Tags are enforced by policy, not convention — see Policy as code. Untagged resources are reported and, where the policy allows, blocked at creation.

Code review​

A Terraform change is reviewed for four things:

  1. Blast radius — what can this plan destroy or replace?
  2. State impact — does it move or recreate resources unexpectedly?
  3. Security effect — does it widen a permission, a network path or an encryption boundary?
  4. Recoverability — if this fails halfway, what is the path back?

A plan that replaces a database requires an explicit migration and rollback plan before approval.