Table of Contents
How Should You Structure a Terraform Project?
A well-structured Terraform project separates concerns, enables reuse, and scales with your team. Here is the recommended structure used by production teams at scale.
Recommended Project Structure
terraform-project/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/
│ └── database/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ └── prod/
├── modules.tf
├── variables.tf
├── outputs.tf
└── versions.tf
Key Principles
1. Separate Environments
Each environment (dev, staging, prod) should have its own directory with its own state file. This prevents accidental changes to production when working on development.
2. Use Modules for Reusable Components
Modules encapsulate related resources. A networking module might include VPC, subnets, route tables, and NAT gateways. This promotes consistency and reduces duplication.
3. Pin All Versions
Always pin Terraform version, provider versions, and module versions:
terraform {
required_version = ">= 1.5.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
4. Use Remote State
Store state in a remote backend (S3, GCS, Terraform Cloud) with locking enabled:
backend "s3" {
bucket = "company-terraform-state"
key = "env/dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
5. Naming Conventions
Use consistent, descriptive names: resource_type-project-environment-region-purpose.
Anti-Patterns to Avoid
- Monolithic configurations — one giant main.tf with hundreds of resources
- Hardcoded values — use variables and locals instead
- Shared state across environments — always separate state per environment
- No version pinning — leads to unexpected breaking changes
Learn More
- Terraform for Beginners Course — hands-on project structure labs
- Terraform By Example Book — real-world patterns
- Terraform Cheat Sheet — quick command reference



