Terraform Best Practices: How to Write Clean, Maintainable, and Scalable IaC
Computer Engineer | DevOps & Cloud Enthusiast | Building scalable apps & automating everything that can be automated 💡 | Writing to simplify tech & share real-world learnings

If you’ve ever opened an old Terraform project and instantly regretted your life choices… welcome to the club. 😄
Terraform is powerful, but it can become a complete mess if you don’t follow some basic best practices. And the truth is — writing Terraform the right way isn’t just about making the code pretty. It’s about:
avoiding infrastructure mistakes
making changes safely
letting other teammates understand your work
and saving your future self from pain
This is a practical, no-fluff guide on how to write Terraform code the right way, explained like how real DevOps engineers talk.
Keep Resources Small and Logical
One of the biggest mistakes beginners make is dumping everything into a single massive .tf file.
Instead:
Break your config into small, meaningful files.
For example:
vpc.tf
security-groups.tf
ec2.tf
s3.tf
outputs.tf
variables.tf
providers.tf
This is not for Terraform.
This is for humans — so you can actually find things later.
Use Variables (Stop Hardcoding Stuff)
Hardcoding things like AMI IDs, instance sizes, bucket names, etc., feels convenient… until the moment you need to reuse the code.
Use variables:
variable "instance_type" {
default = "t3.micro"
}
Now changing the instance type doesn’t require digging around files.
You pass it once → Terraform handles the rest.
Give Things Meaningful Names
Don’t do this:
resource "aws_instance" "server1" { ... }
Name things based on purpose:
aws_instance.web_server
aws_instance.app_server
aws_instance.db_server
Terraform is basically documentation for your infrastructure.
Write names that make sense even 6 months later.
Use Outputs to Surface Important Details
Most beginners don’t use outputs properly.
Outputs allow you to easily expose important info, like:
EC2 public IP
VPC ID
S3 bucket name
RDS endpoint
Example:
output "ec2_public_ip" {
value = aws_instance.web_server.public_ip
}
Now you don’t need to hunt for things in the AWS console.
Avoid Local State
Terraform generates a terraform.tfstate file.
If you run Terraform locally:
you can lose state
teammates can’t sync
infra drifts
mistakes multiply
Always use remote state (S3, Terraform Cloud, GCP Storage, Azure Blob).
And always enable state locking (like DynamoDB lock for AWS).
Format and Validate Before Every Commit
Just like you format Python or JavaScript code, Terraform code should stay clean.
Run:
terraform fmt
terraform validate
These two alone prevent most rookie mistakes.
Use Modules — Don’t Repeat Yourself
If you find yourself copying the same EC2 block or VPC block across files, stop and turn it into a module.
Example:
module "web" {
source = "./modules/ec2"
instance_type = "t3.micro"
}
Modules keep your code:
reusable
clean
scalable
maintainable
Always Check Your Plan Before Applying
Never run:
terraform apply
without checking:
terraform plan
Terraform will do exactly what the code says — even if that means accidentally deleting a production database.
Plan first.
Apply after thinking.
Tag Everything
Tags make your cloud resources organized and traceable.
For AWS:
tags = {
Name = "web-server"
Environment = "dev"
Owner = "Yadnesh"
}
Helps with:
billing
debugging
management
Good Terraform code = good tags.
Separate Environments (don’t mix dev and prod)
Don’t pass variables like:
terraform apply -var env=prod
terraform apply -var env=dev
This is dangerous.
Keep separate folders:
environments/
dev/
staging/
prod/
Each environment gets its own state and config.
This prevents accidental production disasters.
Keep the Lock File
.terraform.lock.hcl locks provider versions.
Don’t delete it.
It keeps your setup stable and prevents surprise breaks caused by new provider releases.
Pin Provider Versions
Specify versions explicitly:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
This keeps behavior consistent across environments.
Comment Only When Needed
Good Terraform code is self-explanatory.
But comment whenever there’s a business rule or something that isn’t obvious.
# This SG allows Jenkins to reach the app server
Clear. Simple. Helpful.
Don’t Mix Multiple Providers in One Folder
If you’re using AWS, Cloudflare, GitHub, Datadog, etc., keep them separate.
This avoids messy, tangled projects.
Treat Terraform Like Real Code
Terraform isn’t “just infra code.”
It has structure, design principles, and patterns.
Readable > clever
Consistent > complex
Predictable > magical hacks
Good Terraform feels like good software.
Final Thoughts
Terraform becomes a joy to work with when you write it like clean software instead of random scripts. Most Terraform headaches come from:
messy files
no modules
local state
poor naming
no tagging
skipping
planno folder structure
Follow these best practices and everything becomes easier:
debugging
teamwork
scaling
reusability
onboarding new engineers
Your future self will thank you for clean Terraform.
