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:
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 Name | Purpose |
main.tf | Main infrastructure configuration and resources |
variables.tf | Input variable declarations |
outputs.tf | Output value definitions |
terraform.tfvars | Variable values for input variables (often used per environment) |
providers.tf | Provider and backend configuration (optional but recommended) |
versions.tf | Terraform 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

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.
- Separate Configuration by Environment
Keep environment specific files in separate directories. This avoids any accidental cross-environment changes.
environments/
├── dev/
├── staging/
└── prod/
- 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”
…
}
- Follow File Naming Conventions
Stick to easy file names, like main.tf, variables.tf, and output.tf to make the codebase easy to understand.
- Keep Code DRY (Don’t Repeat Yourself)
Avoid copy pasting blocks while working with multiple environments, instead use modules and shared variables.
- 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”
}
}
}
- 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”
}
- Use Remote State Storage
Store state files in remote clouds, such as AWS S3, Azure Blob, or Terraform Cloud to support team collaborations.
- Keep Sensitive Data Out of Code
Avoid hardcoding credentials or secret information. Use environment variables, secret managers, or tools like terraform-provider-vault.
- Use terraform.tfvars for Input Values
Store variable values in .tfvars files, ideally per environment, to separate configuration from logic.
- 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.