🌍 What Is a Terraform Project Structure?

Terraform project structure defines how you organize files, modules, environments, and state so that:

  • Infra is scalable
  • Teams can collaborate safely
  • Changes don’t break production
  • CI/CD can manage it cleanly

🧱 Core Principles (Interview Gold)

When explaining structure, always anchor to these:

  1. Separation of concerns
  2. Reusability via modules
  3. Environment isolation
  4. Remote state & locking
  5. Least privilege
  6. CI/CD friendly

βœ… Standard & Scalable Layout

terraform-aws/
β”œβ”€β”€ modules/
β”‚   β”œβ”€β”€ vpc/
β”‚   β”‚   β”œβ”€β”€ main.tf
β”‚   β”‚   β”œβ”€β”€ variables.tf
β”‚   β”‚   └── outputs.tf
β”‚   β”œβ”€β”€ ec2/
β”‚   β”œβ”€β”€ rds/
β”‚   └── alb/
β”‚
β”œβ”€β”€ envs/
β”‚   β”œβ”€β”€ dev/
β”‚   β”‚   β”œβ”€β”€ main.tf
β”‚   β”‚   β”œβ”€β”€ terraform.tfvars
β”‚   β”‚   └── backend.tf
β”‚   β”œβ”€β”€ staging/
β”‚   └── prod/
β”‚
β”œβ”€β”€ provider.tf
β”œβ”€β”€ versions.tf
└── README.md

🧩 Why This Structure Works

FolderPurpose
modules/Reusable infra components
envs/Environment-specific configuration
backend.tfRemote state per env
terraform.tfvarsEnv-specific values

πŸ”Ή modules/ (MOST IMPORTANT)

Rule: One module = One responsibility

Example: modules/vpc

# modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block = var.cidr
}
# modules/vpc/variables.tf
variable "cidr" {
  type = string
}
# modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.this.id
}

πŸ‘‰ Modules must never:

  • Contain backend config
  • Hardcode values
  • Reference environments directly

πŸ”Ή envs/ (Environment Isolation)

Each environment has its own state file.

Example: envs/dev/main.tf

module "vpc" {
  source = "../../modules/vpc"
  cidr   = var.vpc_cidr
}

terraform.tfvars

vpc_cidr = "10.0.0.0/16"

backend.tf

terraform {
  backend "s3" {
    bucket         = "my-tf-state-dev"
    key            = "vpc/terraform.tfstate"
    region         = "ap-south-1"
    dynamodb_table = "tf-lock"
  }
}

πŸ” Remote State Best Practices (AWS)

Use:

  • S3 β†’ state storage
  • DynamoDB β†’ state locking

Why?

  • Prevents concurrent apply
  • Enables team collaboratio
  • Disaster recovery

🧠 Interview Tip:

β€œEach environment has an isolated state backend to avoid cross-environment impact.”


πŸ”„ versions.tf (Pin Everything)

terraform {
  required_version = ">= 1.5.0"
 
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

πŸ‘‰ Prevents breaking upgrades.


πŸ”Ή provider.tf

provider "aws" {
  region = var.region
}

Use variables for:

  • Region
  • Account (via profiles or assume role)

πŸ”’ Security Best Practices

❌ NEVER:

  • Commit .tfstate
  • Commit secrets in .tfvars

βœ… ALWAYS:

*.tfstate
*.tfstate.backup
.terraform/

Use:

  • AWS Secrets Manager
  • SSM Parameter Store
  • Vault

πŸ” CI/CD Friendly Structure

Pipeline example:

cd envs/dev
terraform init
terraform plan
terraform apply

Prod:

  • Manual approval
  • Read-only plan
  • Restricted IAM role

πŸ§ͺ Small vs Large Team Structures

Small Team

terraform/
β”œβ”€β”€ main.tf
β”œβ”€β”€ variables.tf
└── outputs.tf
terraform/
β”œβ”€β”€ modules/
β”œβ”€β”€ envs/
β”œβ”€β”€ policies/
└── pipelines/

🚫 Common Bad Practices (Say This in Interviews)

  • One state file for all envs ❌
  • Hardcoded AWS credentials ❌
  • No module abstraction ❌
  • No state locking ❌
  • Applying from local laptops in prod ❌

🧾 Interview-Ready Summary (MEMORIZE)

β€œIn AWS Terraform projects, we use a module-based structure with environment isolation. Each environment has its own backend and tfvars, remote state stored in S3 with DynamoDB locking, provider and version pinning, and CI/CD-controlled applies.”

πŸ”₯ This answer signals real production experience.