Example Terraform Project Structure: Best Practices for Organizing IaC Code

Example terraform project structure

Table of Contents

Get up to 50% off now

Become a partner with CyberPanel and gain access to an incredible offer of up to 50% off on CyberPanel add-ons. Plus, as a partner, you’ll also benefit from comprehensive marketing support and a whole lot more. Join us on this journey today!

As Infrastructure as Code is becoming increasingly popular in the modern cloud infrastructure management, Terraform stands out as a powerful and flexible tool for provisioning resources across providers. However, as the projects grow due to complexity, it is important to understand the right Terraform project structure. 

In this guide we shall walk through an example Terraform project structure to ensure maintainability, reusability, and scalability. Whether you are managing a single environment setup or juggling multiple environments like dev, staging, and production, a solid project structure will help streamline all your workflows! 

Why Structure Matters in Terraform Projects

A clean and consistent project structure in Terraform is not just about the aesthetics, it is an essential for scalability, collaboration, and long-term maintainability. Poorly organized codes can lead to misconfigured infrastructure, difficulty in onboarding new team members, and an increase in errors. 

Here are a few key reasons why structure matters:

  • Helps with effective collaboration so that teams can understand, navigate, and modify infrastructure code following a consistent layout. 
  • Properly structured code allows you to easily reuse components across different environments or projects. 
  • As the infrastructure grows, a solid foundation makes it easy to expand. 
  • Troubleshooting becomes much more straightforward when the files are logically organized and separated. 
  • Promotes easy automation when the CI/CD pipelines and Terraform rely on predictable file structure. 

Basic Terraform Project Layout Explained

While the tool itself is pretty flexible, following a standard file layout helps keep the project pretty maintainable. 

Main Components (main.tf, variables.tf, outputs.tf)

Here are the foundational files that are commonly found in any Terraform project: 

Tech Delivered to Your Inbox!

Get exclusive access to all things tech-savvy, and be the first to receive 

the latest updates directly in your inbox.

  • main.tf
    main.tf is a core configuration file where you define your infrastructure resources with compute instances, storage instances, storage buckets, or network components, pretty much like a blueprint. 
  • variables.tf
    variables.tf declares all the input variables used throughout the configuration. Defining variables improves flexibility and makes the code reusable. 
  • outputs.tf
    outputs.tf declares the output values, which is the usable information that you want Terraform to display after a successful instance. 

File Naming and Purpose

Terraform doesn’t enforce specific file names, but convention matters for team readability and consistency. Here’s a quick breakdown:

File NamePurpose
main.tfMain infrastructure configuration and resources
variables.tfInput variable declarations
outputs.tfOutput value definitions
terraform.tfvarsVariable values for input variables (often used per environment)
providers.tfProvider and backend configuration (optional but recommended)
versions.tfTerraform and provider version constraints

Recommended Directory Structure for Maximum Usability

Terraform allows you to be flexible in how you structure your projects, but following a consistent directory layout ensures better collaboration and more predictable automation. Below are the recommended approaches for both single and multi-environment projects. 

Single-Environment Project

For small-scale projects or prototypes, a flat structure is sufficient. Here’s a basic layout:

terraform-project/

├── main.tf

├── variables.tf

├── outputs.tf

├── terraform.tfvars

├── providers.tf

└── versions.tf

Enhance Your CyerPanel Experience Today!
Discover a world of enhanced features and show your support for our ongoing development with CyberPanel add-ons. Elevate your experience today!

When to use:

  • Simple applications or services
  • One environment (e.g., only “production”)
  • Limited number of resources

Multi-Environment Setup (Dev, Staging, Prod)

For complex deployments, it is best to separate the configurations by environment to prevent accidental changes across multiple environments.

terraform-project/

├── environments/

│   ├── dev/

│   │   ├── main.tf

│   │   ├── variables.tf

│   │   ├── terraform.tfvars

│   │   └── backend.tf

│   ├── staging/

│   │   └── … 

│   └── prod/

│       └── …

├── modules/

│   ├── network/

│   │   ├── main.tf

│   │   ├── variables.tf

│   │   └── outputs.tf

│   ├── compute/

│   └── …

└── versions.tf

Advantages of this structure:

  • Clean separation between environments
  • Supports different backends, variable values, and resource sizes
  • Encourages reusability through shared modules

Modules Directory

Modules are reusable, self-contained packages of Terraform configurations. They promote DRY (Don’t Repeat Yourself) principles and help maintain consistency across environments.

A typical modules/ directory contains subfolders, each representing a module:

modules/

├── vpc/

│   ├── main.tf

│   ├── variables.tf

│   └── outputs.tf

├── ec2/

└── rds/

You can reference these local modules from environment-specific code:

module “vpc” {

  source = “../../modules/vpc”

  …

}

Using Modules in Terraform Projects

Modules are the building blocks of the scalable and maintainable Terraform projects. They would allow you to group resources together and reuse them across different parts of the infrastructure. 

Local Modules

Local modules are stored within the project’s directory structure, typically under the /modules folder. They’re useful when: 

  • You want to share code between environments (e.g., dev, staging, prod)
  • Your team is small and working from a single repository
  • You’re developing custom, organization-specific infrastructure components

Example structure:

terraform-project/

├── modules/

│   ├── vpc/

│   │   ├── main.tf

│   │   ├── variables.tf

│   │   └── outputs.tf

│   ├── compute/

│   └── rds/

└── environments/

    └── dev/

        └── main.tf

Usage in a configuration:

module “vpc” {

  source = “../../modules/vpc”

  cidr_block = “10.0.0.0/16”

}

Remote Modules

Remote modules are generally hosted outside the project, typically on a version-controlled platform like GitHub, GitLab, or other similar projects. They are the ideal scenario when: 

  • You need to reuse modules 
  • You are officially maintaining modules 
  • You use centralized module development 

Usage example (from the Terraform Registry):

module “vpc” {

  source  = “terraform-aws-modules/vpc/aws”

  version = “5.1.0”

  name = “my-vpc”

  cidr = “10.0.0.0/16”

  …

}

GitHub-based module usage:

module “vpc” {

  source = “git::https://github.com/my-org/terraform-aws-vpc.git?ref=v1.0.0”

  …

}

Related Article: OpenTofu vs Terraform: Key Differences, Pros, and Use Cases

Sample Terraform Project Tree – Example Terraform Project Structure

Here is an example Terraform project structure that is well-organized and supports multiple environments and uses reusable modules. 

terraform-project/

├── environments/

│   ├── dev/

│   │   ├── main.tf

│   │   ├── variables.tf

│   │   ├── outputs.tf

│   │   ├── terraform.tfvars

│   │   └── backend.tf

│   ├── staging/

│   │   ├── main.tf

│   │   ├── variables.tf

│   │   ├── outputs.tf

│   │   ├── terraform.tfvars

│   │   └── backend.tf

│   └── prod/

│       ├── main.tf

│       ├── variables.tf

│       ├── outputs.tf

│       ├── terraform.tfvars

│       └── backend.tf

├── modules/

│   ├── vpc/

│   │   ├── main.tf

│   │   ├── variables.tf

│   │   └── outputs.tf

│   ├── ec2/

│   │   ├── main.tf

│   │   ├── variables.tf

│   │   └── outputs.tf

│   └── rds/

│       ├── main.tf

│       ├── variables.tf

│       └── outputs.tf

├── versions.tf

└── README.md

Highlights of This Structure:

  • environments/ folder contains isolated configurations for each environment (dev, staging, prod), helping prevent cross-environment changes.
  • modules/ folder holds reusable components like vpc, ec2, and rds for consistent infrastructure across environments.
  • Each environment can specify its own backend.tf for remote state management and its own terraform.tfvarsfor variable overrides.
  • versions.tf ensures Terraform and provider versions are consistent across the project.

Best Practices for Organizing Terraform Code

Organizing your Terraform code effectively can make all the difference. Below are some of the best practices that you should follow while structuring your Terraform projects. 

  1. Separate Configuration by Environment

Keep environment specific files in separate directories. This avoids any accidental cross-environment changes. 

environments/

├── dev/

├── staging/

└── prod/

  1. Use Modules for Reusability

Encapsulate the components that are used repeatedly, such as VPCs, EC2 instances, or databases into modules. 

module “vpc” {

  source = “../../modules/vpc”

  …

}

  1. Follow File Naming Conventions

Stick to easy file names, like main.tf, variables.tf, and output.tf to make the codebase easy to understand. 

  1. Keep Code DRY (Don’t Repeat Yourself)

Avoid copy pasting blocks while working with multiple environments, instead use modules and shared variables. 

  1. Use a versions.tf File

Explicitly declare Terraform and provide versions in a versions.tf file to ensure consistent behavior. 

terraform {

  required_version = “>= 1.4.0”

  required_providers {

    aws = {

      source  = “hashicorp/aws”

      version = “~> 5.0”

    }

  }

}

  1. Organize Variables and Outputs

Group variables that are related to each other. Use descriptive names with clear documentation. 

variable “instance_type” {

  description = “EC2 instance type”

  type        = string

  default     = “t3.micro”

}

  1. Use Remote State Storage

Store state files in remote clouds, such as AWS S3, Azure Blob, or Terraform Cloud to support team collaborations. 

  1. Keep Sensitive Data Out of Code

Avoid hardcoding credentials or secret information. Use environment variables, secret managers, or tools like terraform-provider-vault. 

  1. Use terraform.tfvars for Input Values

Store variable values in .tfvars files, ideally per environment, to separate configuration from logic.

  1. Document Your Code

Add comments and documentation clear for yourself and your team in the future! 

Wrapping Up – Example Terraform Project Structure 

Using this example Terraform project structure, you can easily create codes that are structured, clean, and reusable. This guide will help you practice clean coding that is reusable and documented for your future teams! 

Frequently Asked Questions

Should I use a monorepo or split Terraform projects by environment?

It depends on your team and infrastructure scale. For small teams, a monorepo may suffice, while larger organizations benefit from separating by environment (dev, staging, prod).

What are Terraform modules and why use them?

Modules are reusable components that help you manage common configurations (like networking, compute, etc.) across multiple projects, reducing duplication and improving consistency.

How do I manage secrets in a Terraform project?

Use environment variables, secret management tools (like Vault or AWS Secrets Manager), or encrypted backends. Never hard-code secrets in your .tf files or commit them to version control.

Marium Fahim
Hi! I am Marium, and I am a full-time content marketer fueled by an iced coffee. I mainly write about tech, and I absolutely love doing opinion-based pieces. Hit me up at [email protected].
Unlock Benefits

Become a Community Member

SIMPLIFY SETUP, MAXIMIZE EFFICIENCY!
Setting up CyberPanel is a breeze. We’ll handle the installation so you can concentrate on your website. Start now for a secure, stable, and blazing-fast performance!