Skip to main content

Command Palette

Search for a command to run...

Terraform Best Practices: How to Write Clean, Maintainable, and Scalable IaC

Published
4 min read
Y

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 plan

  • no 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.