Day06: Terraform Project Structure
Complete Hands-On Guide
Transform your single main.tf into a production-ready, multi-file Terraform project. This follows HashiCorp best practices for organizing AWS infrastructure code across multiple files.
Why Multiple Files Matter
Single file → Multiple files evolution - Started with everything in main.tf (Day 4) to learn basics, now improving for real-world use.
Key Benefits:
Single Responsibility - Each file has one clear purpose
Team Collaboration - Multiple developers work simultaneously
Better Git History - Focused, reviewable changes
Scalable Foundation - Ready for modules (coming later)
Complete Project Structure
day-06-terraform-project/
├── main.tf # All resource definitions (S3, VPC, EC2)
├── variables.tf # Input variable declarations
├── locals.tf # Local computed values
├── outputs.tf # Output values
├── providers.tf # AWS provider configuration
├── versions.tf # Terraform & provider versions
├── backend.tf # S3 remote state backend
├── terraform.tfvars # Environment variables (NEVER commit!)
├── terraform.tfvars.example # GitHub-safe variable template
├──.gitignore # Exclude sensitive files
└── README.md # Project documentation
Root directory = Root Module - Terraform auto-detects all .tf files.
File-by-File Implementation Guide
1. main.tf - Infrastructure Resources
Purpose: Define all AWS resources (S3, VPC, EC2, etc.)
// create S3 bucket
resource "aws_s3_bucket" "first_bucket" {
bucket = "anjali-gupta-bucket-76"
# region = "eu-north-1"
region = var.region
tags = {
Name = "My bucket"
Environment = var.environment
}
}
// create one sample resource
resource "aws_vpc" "sample" {
cidr_block = "10.0.1.0/24"
# region = "eu-north-1"
region = var.region
tags = {
Environment=var.environment
# Name= "${var.environment}-VPC"
Name= local.vpc_name
}
}
// create one ec2 instance
resource "aws_instance" "example" {
ami = "resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
instance_type = "t2.micro"
# region = "eu-north-1"
region = var.region
tags = {
Environment=var.environment
# Name = "${var.environment}-Instance"
Name = local.instance_name
}
}
When to split further: s3.tf, vpc.tf, ec2.tf for 100+ resources.
2. variables.tf - Input Configuration
Purpose: Declare all configurable parameters with types and descriptions.
// variable named it environment
variable "environment" {
type = string
default = "dev"
}
variable "region" {
type = string
default = "eu-north-1"
}
3. locals.tf - Reusable Computed Values
Purpose: Complex expressions used across resources.
// locals
# locals {
# // can use like this also
# env = var.environment
# vpc_name = "${local.env}-VPC"
# instance_name = "${local.env}-Instance"
# }
locals {
vpc_name = "${var.environment}-VPC"
instance_name = "${var.environment}-Instance"
}
4. outputs.tf - Resource Information
Purpose: Expose key values for other modules/CLI.
output "instance_id" {
value = aws_instance.example.id
}
output "vpc_id" {
value = aws_vpc.sample.id
}
5. providers.tf - AWS Provider Setup
Purpose: Provider configuration and aliases.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "eu-north-1"
}
6. versions.tf - Version Constraints
Purpose: Ensure reproducible builds.
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
7. backend.tf - Remote State Management
Purpose: S3 backend with DynamoDB locking.
terraform {
backend "s3" {
bucket = "anjali-gupta-bucket-76"
key = "terraform.tfstate"
region = "eu-north-1"
use_lockfile = true
}
}
Git Security Checklist (.gitignore)
Critical: Never commit sensitive data to GitHub.
.terraform*
*.tfstate
*.tfstate.backup
.terraform.lock.hcl
crash.log
*.log
terraform.tfvars
*.tfvars.json
.terraform/
terraform.tfvars.example (GitHub Safe)
aws_region = "us-east-1"
environment = "dev"
bucket_name = "my-app-bucket-123"
vpc_cidr = "10.0.0.0/16"
ami_id = "ami-0abcdef1234567890"
instance_type = "t3.micro"
Workflow:
cp terraform.tfvars.example terraform.tfvarsEdit sensitive values in
terraform.tfvarsCommit only
.examplefile
Demo sequence:
Cut backend block →
backend.tfCut provider block →
providers.tfCut variable declarations →
variables.tfCut locals block →
locals.tfCut outputs →
outputs.tfCreate
.gitignore+terraform.tfvars.example
Result: Clean, green VSCode - Terraform auto-detects all files!
Advanced Multi-Environment Structure
terraform-project/
│
├── README.md # Project documentation
├── .gitignore # Git ignore file for Terraform
├── .terraform-version # Terraform version specification
│
└── environments/ # Environment specific configurations
│
├── dev/
│ ├── main.tf # Main configuration for dev
│ ├── variables.tf # Variable declarations
│ ├── terraform.tfvars # Variable values
│ ├── outputs.tf # Output values
│ └── backend.tf # Backend configuration
│
└── staging/
├── main.tf
├── variables.tf
├── terraform.tfvars
├── outputs.tf
└── backend.tf
Module Based Approach:

Two strategies:
Separate main.tf per environment - Different resources
Shared main.tf + environment tfvars - Same infra, different values
Quick Start Commands
# Initialize (downloads providers)
terraform init
# Plan (shows what will change)
terraform plan
# Apply (creates infrastructure)
terraform apply
# Destroy (removes everything)
terraform destroy
Key Takeaways
Root Module = Root directory with
.tffilesFlexible naming -
outputs.tforoutput.tfboth workModules later - For complex projects with 100+ resources
S3 Backend = Team collaboration essential
Gitignore = Security first, never commit state files
tfvars.example = Shareable template for GitHub
Video:
#30daysTerraformwithAWS