# Terraform and OpenTofu Project Structure: Recommended Layouts

> How to organize a Terraform or OpenTofu project: files, modules, environments, state splitting, versions and a monorepo layout that scales with the team.

- Source: https://www.itwonderlab.com/terraform-project-structure/
- Published: 2026-07-21
- Updated: 2026-07-21
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Organize the code so that it can grow

The right structure depends on the size of the project, but the principles are the same: small [state](https://www.itwonderlab.com/terraform-state/) files, reusable [modules](https://www.itwonderlab.com/terraform-module/), explicit environments and consistent names. Start simple and split when something hurts.

### Level 1: a single configuration

For a small project or a tutorial:

```
infra/
  main.tf          # resources
  variables.tf     # inputs
  outputs.tf       # outputs
  providers.tf     # provider configuration
  versions.tf      # required_version and required_providers
  terraform.tfvars # values
  README.md
```

Terraform reads every `.tf` file of the directory, so you can split `main.tf` by topic (`network.tf`, `compute.tf`, `database.tf`) when it gets long. Pin the versions:

```hcl title="versions.tf"
terraform {
  required_version = ">= 1.6"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
```

Commit `.terraform.lock.hcl` so everyone uses the same provider builds, and ignore `.terraform/`, `*.tfstate` and `*.tfplan`.

### Level 2: modules and environments

When you need the same infrastructure in several environments:

```
infra/
  modules/
    network/
      main.tf
      variables.tf
      outputs.tf
    app/
    database/
  environments/
    dev/
      main.tf          # module "network" { source = "../../modules/network" ... }
      backend.tf
      terraform.tfvars
    pro/
      main.tf
      backend.tf
      terraform.tfvars
```

Each environment has its own backend and state. See [workspaces vs directories](https://www.itwonderlab.com/terraform-workspaces-vs-directories/) for the alternatives and [Terraform modules](https://www.itwonderlab.com/aws-terraform-tutorial-terraform-modules/) to write them.

### Level 3: split by layer and lifecycle

A single state for everything makes every `plan` slow and every mistake dangerous. Split by how often things change and who owns them:

```
infra/
  live/
    pro/
      eu-west-1/
        00-account/       # IAM, organizations, KMS
        10-network/       # VPC, subnets, routing
        20-data/          # RDS, S3
        30-apps/          # ECS, EC2, load balancers
```

Lower layers change rarely and are read by upper layers with [data sources or SSM](https://www.itwonderlab.com/terraform-data-sources-remote-state/). [Terragrunt](https://www.itwonderlab.com/terragrunt-opentofu/) automates the dependencies between layers.

### Modules: local, versioned and shared

- Keep modules small and focused on one thing (a network, a database), with clear inputs and [outputs](https://www.itwonderlab.com/terraform-variables-outputs-locals/).
- In a monorepo, reference them with a relative path. When many teams consume them, publish them in a Git repository or a registry and pin the version:

```hcl title="main.tf"
module "network" {
  source = "git::https://github.com/my-org/terraform-modules.git//network?ref=v1.4.0"
}
```

- Do not put provider configurations inside reusable modules: pass them from the caller.
- See [share Terraform projects](https://www.itwonderlab.com/share-terraform-projects/).

### Conventions

- Names with snake_case, resources named `this` or by role when there is only one of a kind.
- Follow a [naming and tagging convention](https://www.itwonderlab.com/aws-and-terraform-naming-best-practices/).
- Document every variable and output with `description`.
- Run `fmt`, `validate`, [lint and security scan](https://www.itwonderlab.com/terraform-security-scanning-tflint-checkov-trivy/) automatically in [CI](https://www.itwonderlab.com/terraform-github-actions-aws-oidc/).
- Add a README per module and example configurations in `examples/`, which also serve as [tests](https://www.itwonderlab.com/terraform-testing-opentofu-test/).

### Signs that you must split the state

- `plan` takes more than a few minutes.
- Different teams need to change different parts at the same time and wait for the lock.
- A small change could break something critical, such as the network.
- The blast radius of one wrong `apply` is bigger than you can accept.

For the complete list of recommendations read [Terraform best practices](https://www.itwonderlab.com/terraform-best-practices/).
