Terraform Tutorial — Project Directory Layout
A well-organized Terraform project is easier to read, review, and maintain. While Terraform doesn’t enforce a directory structure, the community has settled on a standard layout that most teams follow.
Minimal Single-File Project
For quick experiments, you can put everything in one file:
my-project/
└── main.tf
Terraform reads all .tf files in the current directory. Even with a single file, all resources, variables, and outputs work. This is fine for learning, but it doesn’t scale.
Standard Project Layout
For any real project, split your configuration into multiple files:
my-project/
├── main.tf ← Primary resources
├── variables.tf ← Input variable declarations
├── outputs.tf ← Output value declarations
├── providers.tf ← Provider configuration
├── versions.tf ← Terraform and provider version constraints
├── terraform.tfvars ← Variable values (often gitignored)
├── .terraform/ ← Provider cache (never commit)
├── .terraform.lock.hcl ← Dependency lock file (commit this)
├── terraform.tfstate ← State file (never commit)
└── .gitignore ← Ignore state and cache
What Goes in Each File
| File | Contents |
|---|---|
main.tf |
Resource and data source blocks — the core of your infrastructure |
variables.tf |
All variable {} blocks — inputs your config accepts |
outputs.tf |
All output {} blocks — values to display or pass to other modules |
providers.tf |
provider {} blocks — how Terraform connects to AWS/GCP/Azure |
versions.tf |
terraform { required_version, required_providers } block |
terraform.tfvars |
Actual values for variables (e.g., region = "us-east-1") |
💡 Tip: These filenames are conventions, not requirements. Terraform reads all
.tffiles in the directory regardless of their names. The standard names help your team find things quickly.
The .terraform/ Directory
When you run terraform init, Terraform creates a .terraform/ directory containing:
- Downloaded provider binaries (e.g., the AWS provider plugin)
- Downloaded module source code
- Backend configuration cache
This directory is large (hundreds of MB with multiple providers) and is never committed to Git. Every developer runs terraform init to populate it locally.
The .terraform.lock.hcl File
This is Terraform’s dependency lock file. It records the exact versions and checksums of providers downloaded during terraform init.
provider "registry.terraform.io/hashicorp/aws" {
version = "5.31.0"
constraints = "~> 5.0"
hashes = [
"h1:ltYPMnfPhat6NjBMBn0NpsRMwa2dBbKaYZkHlLBbH1A=",
"zh:0cdb9c2083bf0902442c60b3a113e43b42fcee1e8b7... ",
]
}
You should commit this file to Git. It ensures everyone on your team uses the exact same provider versions, preventing “it works on my machine” issues.
State Files
Terraform creates two state files:
| File | Purpose |
|---|---|
terraform.tfstate |
Current state — tracks every resource Terraform manages |
terraform.tfstate.backup |
Backup of the previous state |
Never commit state files to Git. They contain:
- Resource IDs and attributes
- Potentially sensitive data (database passwords, API keys)
- Infrastructure details that could be a security risk
In production, use a remote backend (S3, Terraform Cloud) to store state securely.
Sample .gitignore for Terraform Projects
Every Terraform project should have this .gitignore:
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore CLI configuration files
.terraformrc
terraform.rc
💡 Tip: If your
.tfvarsfile doesn’t contain sensitive data (just region names, environment names), you can track it in Git. But when in doubt,.gitignoreit and document the required variables in aREADME.md.
Files to Commit vs Files to Ignore
| File | Commit to Git? | Why |
|---|---|---|
*.tf |
✅ Yes | Your infrastructure code |
.terraform.lock.hcl |
✅ Yes | Ensures consistent provider versions |
README.md |
✅ Yes | Documentation |
.gitignore |
✅ Yes | Defines what to ignore |
.terraform/ |
❌ No | Large, auto-generated by init |
*.tfstate |
❌ No | Contains sensitive data |
*.tfvars |
⚠️ Depends | Ignore if contains secrets |
Multi-Environment Layout (Preview)
As your project grows, you might structure directories per environment:
terraform/
├── modules/
│ ├── vpc/
│ ├── ec2/
│ └── rds/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── prod/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
This pattern is covered in detail in the Workspaces chapter.
What’s Next?
Your project is organized and ready to go. Let’s verify everything with the chapter’s practice exercises.