Table of Contents
Introduction
A well-structured Terraform project is easier to maintain, debug, and scale. This guide covers proven patterns for organizing your Terraform code, from simple single-environment setups to complex multi-environment architectures.
Basic Project Structure
For small projects or getting started:
project/
├── main.tf # Primary resources
├── variables.tf # Input variable declarations
├── outputs.tf # Output declarations
├── terraform.tfvars # Variable values
├── providers.tf # Provider configuration
└── versions.tf # Version constraints
main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = local.common_tags
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnets[count.index]
availability_zone = var.azs[count.index]
}
providers.tf
provider "aws" {
region = var.region
}
versions.tf
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Multi-Environment Structure
For projects with dev, staging, and production:
project/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── database/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── prod/
│ ├── main.tf
│ ├── terraform.tfvars
│ └── backend.tf
└── README.md
Module Best Practices
Create Reusable Modules
# modules/networking/main.tf
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, { Name = "${var.name}-vpc" })
}
# modules/networking/variables.tf
variable "vpc_cidr" {
type = string
description = "CIDR block for the VPC"
}
variable "name" {
type = string
description = "Name prefix for resources"
}
# modules/networking/outputs.tf
output "vpc_id" {
value = aws_vpc.this.id
}
Use Modules in Environments
# environments/prod/main.tf
module "networking" {
source = "../../modules/networking"
vpc_cidr = "10.0.0.0/16"
name = "prod"
tags = local.common_tags
}
module "compute" {
source = "../../modules/compute"
vpc_id = module.networking.vpc_id
instance_type = "t3.large"
}
Naming Conventions
Consistent naming makes code readable:
# Resources: snake_case, descriptive
resource "aws_instance" "web_server" {}
resource "aws_s3_bucket" "application_logs" {}
# Variables: snake_case
variable "instance_type" {}
variable "vpc_cidr_block" {}
# Outputs: snake_case, prefixed by resource
output "vpc_id" {}
output "web_server_public_ip" {}
# Modules: snake_case
module "networking" {}
module "web_application" {}
File Organization Rules
- One resource type per file for large projects
- Group related resources in smaller projects
- Always separate variables, outputs, and providers
- Use data sources in a dedicated
data.tffile - Keep locals in
locals.tf
Hands-On Courses
- Terraform for Beginners — Project structure deep dive
- Terraform Beginners on CopyPasteLearn
Conclusion
Good project structure pays dividends as your infrastructure grows. Start simple, use modules for reusability, and maintain consistent conventions across your team.

