IaC Starter
Define your cloud infra in Terraform/Pulumi so environments are reproducible and disposable.
Why
Click-ops infrastructure is unreviewable, unreproducible, and undisposable — you can't create a staging env that matches prod because nobody remembers which checkboxes were ticked. Infrastructure as Code makes every cloud resource a reviewed, versioned artifact: a new staging env is 'terraform apply -var-file=staging.tfvars' and it matches prod by construction. When prod drifts, you diff the plan instead of screenshotting a console. The break-even is one recreated environment — if you've ever spent a Friday rebuilding a VPC by hand, you already know the cost of not having IaC.
When
When you have ≥2 cloud resources or ≥2 environments. Takes 2-3 h for Terraform basics. Skip for pure static deploys to a CDN.
How
1. Choose Terraform (HCL, declarative) or Pulumi (TS/Python, programmable)
- Terraform: larger ecosystem, simpler for pure cloud resources
- Pulumi: better if your team already writes TS and wants loops/conditionals
2. Organize by environment: modules/ (reusable) + envs/{staging,prod}/ (composition)
- modules/db/ defines a Postgres instance with all its flags
- envs/staging/main.tf calls that module with staging-specific vars
3. Use a remote state backend (S3+DynamoDB lock, or Terraform Cloud)
- Never use local state in a team — you will get concurrent-apply corruption
- State contains secrets output values; protect the backend with encryption + IAM
4. Make every resource tagged with {Environment, Project, ManagedBy="terraform"}
- Tags let you find unmanaged resources (drift) and cost-allocate in the billing dashboard
5. CI plan-on-PR: terraform plan on every PR, apply on merge to main
- The PR comment shows the plan diff — reviewers see exactly what cloud changes before applyExample
# modules/db/main.tf
resource "aws_db_instance" "main" {
identifier = "${var.env}-db"
engine = "postgres"
instance_class = var.instance_class
allocated_storage = 20
name = "appdb"
username = var.db_user
password = var.db_password # from secrets manager, not plaintext
tags = {
Environment = var.env
ManagedBy = "terraform"
}
}
# envs/staging/main.tf
module "db" {
source = "../../modules/db"
env = "staging"
instance_class = "db.t4g.small"
}Common Problems
- ⚠Storing state locally and getting 'state lock' errors when two devs run apply simultaneously.
- ⚠Putting DB passwords in plaintext variables instead of referencing a secrets manager — they end up in the state file in cleartext.
- ⚠Manually fixing a resource in the console, so Terraform now shows perpetual drift and apply tries to 'fix' it back.
- ⚠One giant main.tf with 500 resources instead of modules — every change risks breaking an unrelated resource.
Best Practices
- ✓Run 'terraform fmt -recursive' and 'terraform validate' in CI before plan — formatting drift is noise in PR diffs.
- ✓Use 'terraform import' to adopt existing hand-created resources instead of destroying and recreating them.
- ✓Mark resources that hold data with lifecycle { prevent_destroy = true } so a bad apply can't delete prod data.
- ✓Keep modules small (≤20 resources) and versioned — a module is a library, treat it with the same review discipline.