Terraform and OpenTofu Best Practices for AWS and the Cloud
A checklist for production-ready infrastructure code #
These practices apply to Terraform and OpenTofu. Each section links to a detailed guide in this site.
State #
- Store the state in a remote backend with locking, versioning and encryption, such as S3 with KMS. See backends.
- Never edit the state by hand and never commit it to Git.
- Split the state by layer and environment to limit the blast radius (project structure).
- Encrypt it on the client with OpenTofu state encryption when possible.
Code #
- Pin the versions of Terraform or OpenTofu and of the providers, and commit
.terraform.lock.hcl. - Write modules for patterns that repeat, and keep them small.
- Use
for_eachinstead ofcountfor collections. - Add
descriptionandtypeto every variable, and validate inputs. - Do not hard-code values: AMIs, regions, account IDs and availability zones come from data sources.
- Use
moved,importandremovedblocks to refactor safely. - Protect critical resources with
prevent_destroyand service-level deletion protection. - Keep one tool for each job: do not mix Terraform with manual console changes. When someone changes something by hand, detect the drift and fix it in code.
Environments #
- One AWS account per environment, with a separate state for each (workspaces vs directories).
- Promote the same module version from dev to pro, not different code.
Security #
- No secrets in code or Git: secrets management.
- Authenticate pipelines with OIDC and least-privilege IAM roles, not static access keys.
- Run security scanners on every pull request.
- Encrypt everything that supports it with KMS and make buckets private by default.
Process #
- Always review the
planbefore theapply. Apply the same saved plan that was reviewed. - Run Terraform only from CI/CD for shared environments.
- Use
-targetonly in emergencies. - Test:
fmt,validate, lint and native tests. - Estimate the cost of changes before merging (Infracost).
- Upgrade providers regularly in small steps and read their changelogs.
Naming and tagging #
- Use a consistent naming convention and apply tags with the provider's
default_tags:
provider "aws" {
region = "eu-west-1"
default_tags {
tags = {
Environment = var.environment
Project = "demo"
ManagedBy = "opentofu"
}
}
}See AWS resource tagging.
Documentation #
- A README per module with inputs, outputs and an example.
- Generate it with
terraform-docs. - Explain decisions in comments, not what the code already says.