Contents
The Two Failure Modes
I've seen both of these in real companies:
The over-engineered startup. Four engineers, no customers yet, and a Terraform repo with 40 modules, three workspaces, Terragrunt on top, and a CI pipeline for infrastructure changes that takes longer than the product deploy. Nobody on the team understands all of it, and the person who wrote it has left.
The click-ops startup. Five years in, 60 engineers, and production was built by hand in the AWS console. Security groups named launch-wizard-7. Nobody knows why a particular IAM role exists, and nobody dares delete it. Standing up a staging environment is a two-week project, and disaster recovery is a hope rather than a plan.
Both are expensive. The first costs you speed when speed matters most. The second costs you reliability, security and auditability when those start to matter. The skill is knowing when to cross over.
What Terraform Actually Gives You
It's worth being precise, because "infrastructure as code" gets sold as a moral good rather than a set of concrete benefits:
- Reproducibility. You can build a second copy of an environment (staging, a new region, a DR account) from the same definition.
- Review. Infrastructure changes go through a pull request. Someone else sees the security group opening port 5432 to
0.0.0.0/0before it happens. - History.
git blametells you why a resource exists and who asked for it. - Drift detection.
terraform plantells you when someone changed something by hand. - Audit and compliance. When SOC 2 or ISO 27001 arrives, "all production changes are peer reviewed and version controlled" is a much easier conversation.
And the costs:
- Learning curve and state management. Remote state, locking, imports and the occasional
terraform state rmare real skills. - Slower small changes. Adding an environment variable becomes a PR, a plan and an apply.
- Module sprawl. Premature abstraction is as painful in HCL as it is in application code.
When I Say "Not Yet"
For a pre-product-market-fit startup, I'm comfortable with not using Terraform if all of these are true:
- You're on a platform that already abstracts infrastructure: Vercel, Render, Fly.io, Railway, Heroku, or a managed backend like Supabase or Firebase.
- There is one production environment and maybe one preview/staging environment the platform gives you for free.
- You don't hold regulated data (health, payments beyond a hosted checkout, children's data).
- The team is small enough that everyone knows what exists.
In this world, the platform is your infrastructure definition. This website runs on Vercel and a managed Postgres database. There's nothing meaningful to Terraform, and adding it would be ceremony.
When I Say "Now"
Adopt Terraform (or OpenTofu, or Pulumi if your team prefers a general-purpose language) as soon as any of these become true:
- You're on AWS, GCP or Azure directly. The moment you have a VPC, IAM roles, RDS, queues and Lambdas, console clicking is accumulating debt quickly.
- You need a second environment that must match the first. Staging that differs from production in unknown ways is worse than no staging.
- You're going through a security certification or selling to enterprises. Their security questionnaires will ask.
- More than one person changes infrastructure. Review is the cheapest security control you'll ever have.
- You've had an incident caused by a manual change. That's the universe telling you.
How to Adopt It Without Drowning
Start with what hurts. Don't try to import the whole estate in a big bang. Start with the resources that change most or matter most: IAM, security groups, databases and DNS. New infrastructure is created in Terraform from day one; old infrastructure is imported opportunistically.
Use the official community modules sparingly and your own modules even more sparingly. A flat, readable set of .tf files per environment beats a clever module hierarchy for the first year. Extract a module when you have three copies of the same thing, not before.
Keep the layout boring:
infra/
modules/ # only when genuinely reused
envs/
staging/
main.tf
backend.tf # S3 state + DynamoDB/S3 lock
production/
main.tf
backend.tfSeparate state by blast radius. Networking, data stores and application services should be in separate state files. A mistake in a Lambda definition should never be able to plan a database replacement.
Plan in CI, apply with a human. Run terraform plan on every pull request and post the output as a comment. Apply after merge, with approval for production. Tools like Atlantis, Spacelift, env0 or plain GitHub Actions all work; pick the least clever one.
Protect the things that hurt to lose. prevent_destroy lifecycle rules and deletion protection on databases, S3 buckets with data and KMS keys. Read every plan that says "must be replaced".
Startup vs Scaleup vs Enterprise
| Stage | Approach |
|---|---|
| Pre-PMF on a PaaS | No Terraform. The platform is your IaC. Document the few manual settings in the README. |
| Early startup on AWS | Terraform for core resources, flat structure, state in S3, plan in CI. |
| Scaleup | Modules for repeated patterns, per-environment and per-domain state, drift detection on a schedule, policy checks (Trivy/Checkov) in CI. |
| Enterprise | Platform team owns golden modules, account vending, policy-as-code (OPA/Sentinel), self-service for product teams. |
A Note on Serverless Frameworks and CDK
If your stack is mostly Lambda, the Serverless Framework, SST or AWS SAM will define functions, queues and API Gateway routes alongside the code. That's fine and often better for application-level resources. I still like Terraform for the shared foundations (VPC, IAM boundaries, databases, DNS) that outlive any one service. The mistake is having three ways to define the same kind of resource and nobody knowing which one owns what.
The Takeaway
Terraform isn't a maturity badge. It's a tool for reproducibility, review and audit. If you don't need those yet because a platform gives them to you, skip it without guilt. But the day you create your first VPC by hand, put a date in the diary to codify it, because the cost of importing an estate grows every month you wait.
