0
0
TerraformHow-ToBeginner · 4 min read

How to Structure a Terraform Project: Best Practices and Example

To structure a Terraform project, organize your code into modules for reusable components and separate environments (like dev, staging, prod) into folders with their own state files. Use a clear folder hierarchy and keep variables, outputs, and provider configurations well defined to maintain clean and scalable infrastructure code.
📐

Syntax

A typical Terraform project structure includes:

  • modules/: reusable code blocks for resources
  • environments/: folders for each deployment environment
  • main.tf: main configuration file
  • variables.tf: input variables definitions
  • outputs.tf: output values
  • terraform.tfvars: variable values per environment

This structure helps separate reusable logic from environment-specific settings.

plaintext
terraform-project/
├── modules/
│   └── network/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars
└── README.md
💻

Example

This example shows a simple Terraform project with a network module and two environments: dev and prod. Each environment calls the module with its own variables and keeps separate state files.

terraform
terraform-project/
├── modules/
│   └── network/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars

# modules/network/main.tf
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr
  tags = { Name = var.vpc_name }
}

# modules/network/variables.tf
variable "vpc_cidr" { type = string }
variable "vpc_name" { type = string }

# modules/network/outputs.tf
output "vpc_id" { value = aws_vpc.main.id }

# environments/dev/main.tf
module "network" {
  source   = "../../modules/network"
  vpc_cidr = var.vpc_cidr
  vpc_name = "dev-vpc"
}

# environments/dev/variables.tf
variable "vpc_cidr" { type = string }

# environments/dev/terraform.tfvars
vpc_cidr = "10.0.0.0/16"

# environments/prod/main.tf
module "network" {
  source   = "../../modules/network"
  vpc_cidr = var.vpc_cidr
  vpc_name = "prod-vpc"
}

# environments/prod/variables.tf
variable "vpc_cidr" { type = string }

# environments/prod/terraform.tfvars
vpc_cidr = "10.1.0.0/16"
Output
Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: vpc_id = vpc-0a1b2c3d4e5f6g7h8
⚠️

Common Pitfalls

Common mistakes when structuring Terraform projects include:

  • Mixing environment configurations in one folder, causing state conflicts.
  • Not using modules, leading to duplicated code and harder maintenance.
  • Storing sensitive data in plain terraform.tfvars files instead of secure secrets management.
  • Using a single state file for multiple environments, risking accidental resource changes.

Always separate environments and use modules for reusable parts.

terraform
### Wrong: Single folder for all environments

# main.tf
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr
}

# terraform.tfvars
vpc_cidr = "10.0.0.0/16"  # Used for all environments, no separation

---

### Right: Separate folders per environment

environments/dev/main.tf
module "network" {
  source   = "../../modules/network"
  vpc_cidr = var.vpc_cidr
}

# terraform.tfvars (dev)
vpc_cidr = "10.0.0.0/16"

environments/prod/main.tf
module "network" {
  source   = "../../modules/network"
  vpc_cidr = var.vpc_cidr
}

# terraform.tfvars (prod)
vpc_cidr = "10.1.0.0/16"
📊

Quick Reference

  • Use modules to keep reusable code separate.
  • Separate environments into folders with their own state files.
  • Keep variables and outputs organized per module and environment.
  • Use remote state backends for team collaboration and state safety.
  • Secure sensitive data with environment variables or secret managers.

Key Takeaways

Organize Terraform code into modules for reusable infrastructure components.
Separate environments into distinct folders with their own state files to avoid conflicts.
Keep variables and outputs clearly defined per module and environment.
Use remote state backends for safe collaboration and state management.
Avoid storing sensitive data in plain files; use secure secrets management.