Terraform and OpenTofu Best Practices for AWS and the Cloud

· 2 min read · Terraform Best Practices

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_each instead of count for collections.
  • Add description and type to every variable, and validate inputs.
  • Do not hard-code values: AMIs, regions, account IDs and availability zones come from data sources.
  • Use moved, import and removed blocks to refactor safely.
  • Protect critical resources with prevent_destroy and 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 plan before the apply. Apply the same saved plan that was reviewed.
  • Run Terraform only from CI/CD for shared environments.
  • Use -target only 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:
providers.tf
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.

Debugging, cheat sheet and AWS best practices.

#Terraform #OpenTofu #Best Practices #AWS