Skip to content
6 min read·Lesson 10 of 10

Terraform Best Practices

Production-proven conventions for structuring Terraform projects, managing secrets, naming resources, writing maintainable code, and working safely in teams.

As Terraform projects grow, structure, consistency, and safety practices become critical. These patterns are used by platform engineering teams managing hundreds of resources across multiple accounts and regions.

Directory Structure

Organise Terraform code by environment and concern — not by resource type:

infrastructure/
  modules/                    # reusable modules
    vpc/
    eks/
    rds/
  environments/
    dev/
      networking/
        main.tf
        variables.tf
        outputs.tf
        terraform.tfvars
      application/
        main.tf
    staging/
      networking/
      application/
    production/
      networking/
      application/

Each environment+concern directory has its own state file. This limits the blast radius — a mistake in production/application cannot accidentally destroy production/networking.

File Organisation Within a Module

main.tf          # primary resources
variables.tf     # all input variable declarations
outputs.tf       # all output declarations
versions.tf      # required_version and required_providers
locals.tf        # local values (in larger modules)
data.tf          # data sources (in larger modules)
README.md        # module documentation

Naming Conventions

# Resource names: snake_case, descriptive
resource "aws_security_group" "alb_ingress" { ... }   # good
resource "aws_security_group" "sg1" { ... }            # bad

# Resource Name tag: consistent pattern
tags = {
  Name = "${var.project}-${var.environment}-${var.component}"
  # e.g.: myapp-production-web
}

# Variables and locals: snake_case
variable "instance_count" { ... }   # good
variable "instanceCount" { ... }    # bad

Tagging Strategy

Apply consistent tags to every resource for cost allocation, security, and operations:

locals {
  required_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
    Owner       = var.team_email
    CostCenter  = var.cost_center
  }
}

# Merge required tags with resource-specific tags
resource "aws_instance" "app" {
  tags = merge(local.required_tags, {
    Name      = "${local.name_prefix}-app"
    Component = "web-server"
  })
}

Security Practices

# .gitignore — ALWAYS include these
*.tfstate
*.tfstate.backup
*.tfvars          # may contain secrets
.terraform/
crash.log
override.tf
  • Never hardcode credentials — use environment variables or IAM roles
  • Mark sensitive variables and outputs as sensitive = true
  • Use a secrets manager (AWS Secrets Manager, HashiCorp Vault) for values that must be retrieved at apply time
  • Enable S3 bucket versioning and encryption for state files
  • Restrict IAM permissions: Terraform should only have the permissions it needs

Version Pinning

terraform {
  required_version = ">= 1.6.0, < 2.0.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"    # allow 5.x patch/minor updates
    }
  }
}

# Commit .terraform.lock.hcl to ensure all team members
# and CI use the exact same provider version

Testing and Linting

  • terraform fmt -check: Enforce formatting in CI
  • terraform validate: Syntax and type checking
  • tflint: Provider-specific lint rules (catches invalid instance types, deprecated arguments)
  • tfsec / Checkov / KICS: Security scanning — finds open security groups, unencrypted resources
  • Terratest: Go-based testing framework for writing integration tests for Terraform modules
  • terraform test: Native Terraform testing (1.6+)

State Management Safety

  • Never run terraform apply from a local machine against production — use CI/CD
  • Use prevent_destroy = true on databases, state buckets, and other critical resources
  • Use -target sparingly — it leaves state partially applied and can cause drift
  • Keep state files small — split large configurations into multiple state files

You've covered the Terraform fundamentals — IaC concepts, HCL syntax, providers, resources, state, modules, backends, and best practices. Practice by building real AWS infrastructure with Terraform: start with a VPC, add EC2 instances, then try EKS or RDS.

Key Takeaways

  • Use a consistent directory structure: separate directories for each environment and concern.
  • Never store secrets in .tf files or state in Git — use environment variables and remote backends.
  • Pin provider and module versions, and commit .terraform.lock.hcl.
  • Tag every resource consistently for cost allocation, ownership, and compliance.
  • Use terraform fmt, terraform validate, and tflint in every CI pipeline.
🎉

Course Complete!

You've finished Terraform Basics. Now put your knowledge to the test with real exam-style practice questions.