TerraformPilot

Terraform

How to Structure a Terraform Project - Best Practices for 2026

Learn the best way to organize Terraform projects. Covers file structure, modules, environments, and naming conventions for scalable infrastructure code.

LLuca Berton1 min read

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

#
  1. One resource type per file for large projects
  2. Group related resources in smaller projects
  3. Always separate variables, outputs, and providers
  4. Use data sources in a dedicated data.tf file
  5. Keep locals in locals.tf

Hands-On Courses

#

Conclusion

#

Good project structure pays dividends as your infrastructure grows. Start simple, use modules for reusability, and maintain consistent conventions across your team.

#Terraform#Best Practices#DevOps#Infrastructure as Code

Share this article