Terraform on a solo side project tolerates a lot: local state, applying straight from a laptop, one giant main.tf. None of that survives contact with a second engineer running terraform apply against the same infrastructure — here's where it actually breaks, and what we set up instead.
Remote state, with locking
Local state committed to git (or worse, not committed and living only on one laptop) means two people can run apply at the same time and corrupt each other's changes — or one person's local state silently drifts from what's actually deployed. Remote state with locking makes concurrent applies fail loudly instead of corrupting state silently.
terraform {
backend "s3" {
bucket = "your-tfstate-bucket"
key = "prod/terraform.tfstate"
region = "ap-south-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}Modules, not one file that does everything
A single main.tf that provisions the VPC, the compute, the database, and the DNS all in one place is hard to review and impossible to reuse. Splitting into modules (network, compute, database) with clear inputs/outputs means a change to one layer doesn't require re-reading the whole file to understand blast radius.
Plan in CI, apply the exact plan that was reviewed
terraform plan and terraform apply run separately, on a laptop, is how "the plan I reviewed" and "what actually got applied" quietly diverge — someone else may have merged a change in between. Generate a plan file in CI, have a human approve it, then apply that exact file:
terraform plan -out=tfplan
# reviewed and approved in the pipeline
terraform apply tfplanWhat this doesn't solve
Terraform enforces whatever state you've declared — it doesn't know if that state is a good idea. Someone still has to actually read the plan output before approving it, not just click approve because the pipeline is green. We've seen a reviewed-in-name-only approval let a destroy-and-recreate slip through on a resource that should never have been replaced.