Terraform structure is not folder organization. It defines how infrastructure is owned, governed, deployed, and scaled across teams and evironments.
The goal is to make operational boundaries clear: who owns which part of infrastructure, where is the state stored, how changes move from dev to prod, what the blast radius could look like, who the rollback could look like, etc.
On simpler projects it is usually clear: a single repo with remote state and that’s it. The common discussion though is connected to more complex projects and whether you should use single or multi repo setup. There are few things which might influence decision and setup.
In this blog I will try to help you with deciding whether you should use Terraform mono repo vs multi repo setup.
State boundaries
In Terraform, the root module is the deployment boundary. It is the directory where Terraform is initialized, planned, and applied.
Each root module should have its own remote state. For small systems, one root module can be enough. In large infrastructure setups, state is typically separated by lifecycle, ownership, and blast radius so that teams can deploy safely and independently. For example you want to split big chunks of the same project into different state file.
Example:
shared state:
VPC
KMS keys
certificates
common IAM
shared networking
frontend state:
S3 static hosting
CloudFront
WAF
DNS records
service state:
Lambda
API Gateway
SQS
CloudWatch alarms- Splitting state this way avoids locking the entire platform for every small change.
- It also reduces the impact of mistakes.
- If you change frontend infrastructure, you do not want to plan or risk unrelated shared networking resources.
Mono repo
A mono repo keeps modules, stacks, and environment configuration together. Although within mono repo you still can have one or more Terraform entrypoints.
This is example of single entrypoint which is Terraform root module with additional modules and environment files.
infra/
main.tf
variables.tf
outputs.tf
providers.tf
backend.tf
vpc.tf
s3.tf
lambda.tf
cloudfront.tf
modules/
s3/
main.tf
variables.tf
outputs.tf
lambda/
main.tf
variables.tf
outputs.tf
cloudfront/
main.tf
variables.tf
outputs.tf
envs/
dev.tfvars
qa.tfvars
prod.tfvarsYou can run this project from root module:
terraform init
terraform plan -var-file=envs/dev.tfvars
terraform apply -var-file=envs/dev.tfvarsThis is common when one team or single person owns most of the infrastructure, or when the infrastructure pieces are tightly connected or let’s say if the project is rather simple.
Note that if you are, for whatever reason, using one cloud account for multiple environments you need to have state per each env.
The main advantage is consistency. Shared modules are easy to update, global refactors are easier, and reviewers can compare environments in one place.
The downside is that access control and CI/CD can become more complex if many teams work in the same repository. Also since you Terraform project in this case is single deployable unit if you have issues with some parts of the project entire project is blocked.
Mono repo with multiple entrypoints
If the mono repo has 3 Terraform entrypoints, then you usually do not put main.tf, variables.tf, backend.tf and outputs.tf directly in root Terrafrom module. Instead each stack/service directory is the actual Terraform root module.
infra/
modules/
s3/
lambda/
cloudfront/
stacks/
shared/
main.tf
variables.tf
outputs.tf
backend.tf
dev.tfvars
prod.tfvars
frontend/
main.tf
variables.tf
outputs.tf
backend.tf
dev.tfvars
prod.tfvars
notification/
main.tf
variables.tf
outputs.tf
backend.tf
dev.tfvars
prod.tfvarsIn this setup, Terraform is executed from each stack directory:
cd infra/stacks/frontend
terraform init
terraform plan -var-file=dev.tfvarsThis one is also achievable by utilizing Terraform workspaces.
The point is: mono repo does not necessarily mean a single Terraform root module.
This approach is to keep all Terraform code in one repository, but split the infrastructure into multiple stacks. Each stack is its own root module and deployment unit, with its own Terraform configuration and remote state.
Simple mono repo usually works well when:
- One team owns the whole infrastructure
- The project is small or medium-sized
- Infrastructure components share the same lifecycle
- The blast radius of one Terraform state is acceptable
- You want the simplest possible setup
- Environments differ mostly by variable values
Mono repo with multiple stacks usually works well when:
- One repository is still preferred, but infrastructure needs clearer boundaries
- Different parts of the platform have different lifecycles
- You want separate state files for
shared,frontend,notification, etc. - Some stacks should be deployed independently
- You want to reduce the blast radius of each Terraform apply
- Teams or reviewers need clearer ownership per stack
Multi Repo
A multi-repo model gives each service or team its own infrastructure repository.
Example:
platform-infra/
shared networking
identity
security baseline
frontend-infra/
frontend-specific AWS resources
notification-infra/
notification service resourcesA multi-repo setup separates infrastructure by ownership boundary. Shared infrastructure may live in a platform repository, while each application or service has its own infrastructure repository.
This works well when teams need to move independently. Each team can own its deployment lifecycle, review process, permissions, and CI/CD pipeline.
For example, a larger company may keep global management configuration, such as organization-level permissions, policies, billing setup, and security baselines, in a dedicated platform repository with restricted access. Application teams can then manage their own service-specific infrastructure in separate repositories.
The tradeoff is consistency. Since infrastructure code is spread across multiple repositories, teams need stronger standards for module usage, naming conventions, validation, policies, and interfaces between stacks. Documentation and long-term maintenance also become more important.
Multi repo usually works well when:
- Different teams own different services
- Services have different release cycles
- Access control should be separated
- Each service/team needs its own CI/CD pipeline
- Infrastructure boundaries are clearly defined
Remote state works in both models
Terraform remote state is independent of repository structure.
A mono repo can have multiple state files. A multi-repo setup can also have multiple state files. The repository model decides where the code lives; the state design decides the deployment boundaries.
One stack can read outputs from another stack if it knows the backend location and has permission to access it.
data "terraform_remote_state" "shared" {
backend = "s3"
config = {
bucket = "shared-tfstate"
key = "shared/terraform.tfstate"
region = "eu-central-1"
}
}For example, a frontend stack can read VPC or DNS outputs from a shared platform stack, even if they are stored in different repositories. What is important is that state file exists in S3 bucket, that you know correct bucket, key and region, and IAM role/user which runs Terraform has permissions to read that state file.
Conclusion
There is no single best Terraform structure. The right choice depends on project complexity, team ownership, security requirements, environment isolation, and deployment strategy.
Use a mono repo when the infrastructure is mostly owned by one team or person and the project is still simple enough to review and deploy from one place.
Use a mono repo with multiple stacks when you still want one repository, but need separate root modules, separate state files, smaller deployment boundaries and isolation between services to reduce blast radius. Also with people working with this setup you can do parallel work as stacks can be developed and shipped independently.
Use multi repo when different teams own different services, release independently, need separate permissions, require their own CI/CD pipelines, want shared (baseline) services isolated, etc.



