Infrastructure as Code: Terraform Best Practices
After years of managing infrastructure with Terraform across multiple cloud providers and teams, I’ve developed a set of practices that help keep codebases maintainable and deployments reliable.
Project Structure
A well-organized Terraform project is crucial for team collaboration. Here’s the structure I recommend:
infrastructure/
modules/
networking/
main.tf
variables.tf
outputs.tf
compute/
main.tf
variables.tf
outputs.tf
environments/
production/
main.tf
backend.tf
terraform.tfvars
staging/
main.tf
backend.tf
terraform.tfvars
State Management
Remote state is non-negotiable. Use a backend with locking:
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "production/infrastructure.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
State Management Rules
- One state per environment - Never share state between staging and production
- Enable state locking - Prevents concurrent modifications
- Encrypt state at rest - State files contain sensitive data
- Use workspaces sparingly - Prefer separate backend configurations
Module Design
Good modules are reusable, composable, and well-documented:
module "web_cluster" {
source = "../../modules/compute"
environment = var.environment
instance_type = "t3.medium"
min_size = 2
max_size = 10
tags = merge(var.common_tags, {
Service = "web"
})
}
Module Guidelines
- Keep modules focused - One responsibility per module
- Use semantic versioning for shared modules
- Expose only necessary variables - Don’t make everything configurable
- Always define outputs for inter-module communication
- Include validation on variables where appropriate
variable "environment" {
type = string
description = "Deployment environment"
validation {
condition = contains(["production", "staging", "development"], var.environment)
error_message = "Environment must be production, staging, or development."
}
}
CI/CD Integration
Automate your Terraform workflows:
# GitLab CI example
plan:
stage: plan
script:
- terraform init
- terraform plan -out=plan.tfplan
artifacts:
paths:
- plan.tfplan
apply:
stage: deploy
script:
- terraform apply plan.tfplan
when: manual
only:
- main
Common Pitfalls
- Not using
terraform fmt- Enforce formatting in CI - Ignoring drift - Schedule regular
terraform planchecks - Hardcoding values - Use variables and data sources
- Large monolithic states - Split into smaller, focused states
- Missing lifecycle rules - Use
prevent_destroyfor critical resources
Summary
Infrastructure as Code is only as good as the practices around it. Invest time in structure, automation, and team conventions. Your future self will thank you.