File and Directory Structure Conventions
Terraform does not enforce any particular file layout — it loads every .tf file in a directory and merges them into a single configuration regardless of filename. That flexibility means teams must impose their own conventions, and the community has converged on a fairly consistent baseline: main.tf for primary resource definitions, variables.tf for input declarations, outputs.tf for exported values, providers.tf (or versions.tf) for provider and required_version blocks, and terraform.tfvars for default variable assignments. This predictable skeleton lets any engineer open an unfamiliar repository and immediately know where to look for a given concern.
Cricket analogy: Terraform not enforcing file layout is like the laws of cricket not mandating a specific practice schedule, yet every serious academy converges on the same routine — nets, fitness, video review — so any new coach recognizes the structure immediately.
Root Modules vs. Reusable Modules
A typical repository separates a top-level root configuration (the environment-specific entry point that calls modules and holds backend configuration) from a modules/ directory containing reusable, environment-agnostic building blocks such as modules/vpc or modules/eks-cluster. The root module should stay thin — mostly module calls and variable wiring — while the heavy resource logic lives inside the reusable modules, making it possible to version and test modules independently of any single environment.
Cricket analogy: The root module staying thin while modules/ holds the heavy logic is like a captain's thin game plan calling on reusable, well-drilled units — the pace attack, the top order — each independently trained regardless of which match it's deployed in.
Multi-Environment Layouts
For projects managing multiple environments as separate root configurations rather than workspaces, a common pattern is an environments/ directory with one subdirectory per environment (environments/dev, environments/staging, environments/prod), each containing its own backend.tf, terraform.tfvars, and a short main.tf that references shared modules by relative or registry path. This keeps state, credentials, and variable values cleanly partitioned per environment while maximizing code reuse through the shared modules directory.
Cricket analogy: An environments/ directory with dev, staging, and prod subfolders is like separate training venues for the A-team, the reserves, and the senior squad, each with its own kit and schedule but drawing on the same shared coaching modules.
repo/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── eks-cluster/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── environments/
├── dev/
│ ├── main.tf
│ ├── backend.tf
│ └── terraform.tfvars
└── prod/
├── main.tf
├── backend.tf
└── terraform.tfvarsTerraform reads file contents alphabetically-agnostic and merges everything, so main.tf is a human convention, not a technical requirement — you could name every file zzz.tf and it would behave identically. Naming conventions exist purely for the humans who will read the repository later.
Avoid deeply nested or inconsistent module trees where the same logical resource (e.g. a VPC) is defined slightly differently in three different directories. Drift between near-duplicate modules is one of the most common causes of configuration sprawl and unexplained environment differences.
- Terraform loads all
.tffiles in a directory regardless of name; file naming conventions are a human, not technical, requirement. - Common baseline files:
main.tf,variables.tf,outputs.tf,providers.tf/versions.tf,terraform.tfvars. - Separate reusable
modules/from environment-specific root configurations for cleaner reuse and testing. - An
environments/directory with one subfolder per environment keeps state and credentials cleanly partitioned. - Keep root modules thin — mostly module calls and variable wiring — pushing detailed resource logic into modules.
- Consistent structure reduces onboarding time and prevents configuration drift between near-duplicate resource definitions.
Practice what you learned
1. Does Terraform require files to be named main.tf, variables.tf, and outputs.tf?
2. What is the purpose of a modules/ directory in a well-structured Terraform repository?
3. Which file is automatically loaded by Terraform without needing an explicit -var-file flag?
4. In a multi-environment layout using environments/dev and environments/prod, what typically differs between the two directories?
5. What is a key risk of allowing near-duplicate module definitions to accumulate across environment directories?
Was this page helpful?
You May Also Like
Workspaces for Environments
Terraform workspaces let a single configuration manage multiple distinct state files, offering a lightweight way to spin up parallel environments from one codebase.
Terraform Modules Explained
Modules are Terraform's mechanism for packaging and reusing configuration. This topic explains what a module is, root vs. child modules, and how module calls connect inputs, resources, and outputs.
Writing a Reusable Module
Turning ad-hoc configuration into a well-designed reusable module requires thoughtful input/output design, sensible defaults, and internal structure. This topic walks through the practical craft of module authoring.
Remote State Backends
Remote backends store Terraform's state file outside the local disk, enabling team collaboration, durability, and integration with locking mechanisms.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics