All resources
Infrastructure as Code · Practical outline
Terraform Best Practices for Growing Engineering Teams
By Shahrooz Mehmood · · 8 min read
Terraform that works for one engineer often breaks down at five. These practices help keep infrastructure code reviewable, safe to change and easy for new team members to understand.
Terraform provisioning workflow
- Source
- Build
- Security
- Gate
- Deploy
- Observe
- 01Version-controlled TerraformReusable modules, per-environment config
- 02Validate & planfmt, validate, lint and terraform plan
- 03Policy checksSecurity and tagging rules as code
- 04ApprovalPlan reviewed before apply
- 05Cloud provisioningApply with remote state and locking
- 06Monitoring & cost reportingDrift detection, budgets and dashboards
Structure and modules
Design for reuse without building a framework nobody understands.
- Small, focused modules with clear inputs and outputs.
- Version modules and pin versions in consumers.
- Keep environment-specific values out of module code.
- Provide examples and a README for every module.
State management
State is the most sensitive part of a Terraform setup.
- Remote state with locking (e.g. S3 + DynamoDB, Azure Storage, GCS, Terraform Cloud).
- Separate state per environment and per logical component to limit blast radius.
- Encrypt state and restrict access — it can contain secrets.
Workflow and review
Treat infrastructure changes like application changes.
- Run fmt, validate and TFLint on every pull request.
- Post the plan output to the pull request for review.
- Apply only from CI after approval — never from laptops for shared environments.
- Use policy as code (Checkov, OPA/Conftest, Sentinel) for security and tagging rules.
Drift and lifecycle
Keep reality and code aligned over time.
- Schedule drift detection and investigate any differences.
- Import hand-built resources rather than recreating them.
- Use lifecycle rules and prevent_destroy for critical resources.
- Upgrade providers regularly and in isolation.
Cost and ownership
Infrastructure code is the best place to make cost visible.
- Enforce owner, service and environment tags through policy.
- Estimate cost changes on pull requests (e.g. Infracost).
- Review retention settings for logs, snapshots and backups.