Terraform is the default answer to "how do we manage our infrastructure," and for a lot of teams that default is correct. But it became the default in an era when most teams provisioned VMs, networks, and load balancers by hand, and any tool that made that repeatable was a clear win. The shape of a typical backend has changed since then. Before you write your first main.tf, it's worth asking whether Terraform is solving a problem you have.
This guide is a decision, not a tutorial. It splits the question by the kind of team and project you're on, because the answer to "do I need Terraform" is different for a two-person greenfield startup than for a platform team running 40 services across AWS and GCP. We'll cover where Terraform earns its place, where it's overhead you can skip, a checklist to run against your own situation, and the alternatives worth considering in each case.
It helps to be precise about what you're getting, because that's what you're deciding whether you need.
Terraform gives you a declarative description of infrastructure in HCL, a resource graph it uses to compute changes, a state file that records what it believes exists, and a plan/apply cycle that shows you the diff before you commit to it. The provider ecosystem is enormous, so the same tool addresses AWS, GCP, Azure, Cloudflare, Datadog, and hundreds of other APIs through one interface.
The value of that is real and specific:
The cost is also real: a separate language, a separate codebase, a state file you have to store and lock and protect, and a body of module and provider knowledge your team has to carry. Whether you need Terraform is a question of whether the value above outweighs that cost for your situation.
Some teams should not overthink this. If you match these, Terraform (or OpenTofu) is the right tool and the setup cost pays for itself.
You run infrastructure across more than one cloud. The moment your estate spans AWS and GCP, or AWS and Cloudflare, or on-prem and a public cloud, you want one tool and one workflow across all of it. Terraform's provider model is the strongest answer here. Cloud-native tools (CloudFormation, GCP Infrastructure Manager) stop at their own cloud's edge.
You have a dedicated platform or ops team. When provisioning is owned by people whose job is infrastructure, the explicit state, the plan review, and the module libraries are exactly the controls they want. The overhead that feels heavy to an app developer is the workflow a platform team is built around.
You have compliance, audit, or change-control requirements. Regulated environments need a record of every infrastructure change, an approval gate before it lands, and a policy layer that can block non-compliant resources. Terraform plus a policy engine (Sentinel, OPA/Conftest) and a run platform (HCP Terraform, Spacelift, env0) is a well-worn path here that auditors recognize.
You already have a large Terraform estate. If there are thousands of resources under management and a team fluent in the codebase, the question isn't "do we need Terraform," it's "is there any reason to move." Usually there isn't. Migrating a working estate is a large project with little upside unless you have a specific, pressing reason.
You provision infrastructure that isn't application-level. VPCs, subnets, IAM policies, DNS zones, VPN gateways, transit gateways, security groups tuned by hand. This is the domain Terraform was built for, and nothing at the application layer replaces it.
The cases below are where teams reach for Terraform out of habit and pay a setup and maintenance cost that doesn't buy them much.
You're a small team on a single cloud. One or two engineers shipping an app on AWS or GCP. The Terraform setup, remote state backend, state locking, module structure, and CI wiring, is often more moving parts than the infrastructure it manages. A managed platform or an infrastructure-from-code framework will get you a database, a queue, and a cron job with a fraction of the ceremony.
You're greenfield. Starting fresh means you have no existing estate to preserve and no reason to adopt HCL by default. This is the best moment to ask whether the infrastructure your app needs (Postgres, object storage, Pub/Sub, scheduled jobs) can be declared closer to the code that uses it.
Your infrastructure is entirely application-level. If everything you provision is a database, a cache, a queue, a bucket, or a scheduled job, all of it in service of one application, then a separate IaC codebase describing those resources is a second source of truth that has to stay in sync with the first. There are tools that derive the infrastructure from the application directly.
A platform already provisions for you. If you're on Vercel, Render, Railway, Fly, or a similar PaaS, the platform is already provisioning and managing the resources. Adding Terraform on top duplicates work the platform is doing.
You provision rarely and by hand is fine. Some projects touch infrastructure once at setup and then never again. For a handful of resources you'll create once, the cloud console or a short CLI script is faster than standing up a Terraform workflow you'll barely use.
Run your situation through these. The more you answer "yes," the more Terraform earns its place.
If most of those are "no," you're in skip-it territory, and the sections below cover where to go instead.
| Situation | Best fit | Why |
|---|---|---|
| Multi-cloud estate, platform team | Terraform / OpenTofu | Broadest provider coverage, explicit state, policy layer |
| Single cloud, want a general-purpose language | Pulumi / AWS CDK | IaC in TypeScript, Go, Python; keeps state model |
| MPL-licensed Terraform | OpenTofu | Drop-in, permissive license, community governance |
| A few resources, provisioned once | Cloud console / CLI | No workflow to maintain for rarely-touched infra |
| App-level infra, single app, greenfield | Infrastructure from code (Encore) | Infra derived from app code, no separate state |
| Already on a PaaS | The platform itself | It already provisions for you |
If you landed in the skip-it group and your infrastructure is application-level, there's a path that removes the separate IaC layer entirely rather than swapping HCL for another language.
Terraform, Pulumi, and CDK all share one shape: infrastructure is described in a separate codebase, tracked in a separate state file, and applied by a separate tool. Your application code has no awareness of the infrastructure it depends on, and the infrastructure config has no awareness of how the app uses it. When most of your "infrastructure" is a database, a queue, and a couple of scheduled jobs that exist only to serve one application, keeping two synchronized sources of truth is work that doesn't pay off.
Infrastructure from code collapses that. You declare the resources your app needs directly in the application's source, and tooling provisions the real cloud services from those declarations. Encore is the open-source infrastructure-from-code framework for TypeScript and Go, with over 12,000 GitHub stars and production users including Groupon.
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { CronJob } from "encore.dev/cron";
// Provisions managed Postgres (RDS on AWS, Cloud SQL on GCP, Docker locally).
const db = new SQLDatabase("users", { migrations: "./migrations" });
// Runs on a schedule managed by Encore Cloud, no cron infra to wire up.
new CronJob("cleanup", {
title: "Nightly cleanup",
every: "24h",
endpoint: cleanup,
});
export const cleanup = api(
{ method: "POST", path: "/internal/cleanup" },
async () => {
await db.exec`DELETE FROM sessions WHERE expires_at < now()`;
},
);
Here's the equivalent database provisioned the Terraform way, for contrast:
resource "aws_db_instance" "users" {
identifier = "users"
engine = "postgres"
engine_version = "16"
instance_class = "db.t3.micro"
allocated_storage = 20
db_name = "users"
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
skip_final_snapshot = true
}
# plus the subnet group, security group, parameter group,
# variables, and a place to store db_password securely,
# then the migrations run through some separate mechanism.
The Terraform version is more explicit and gives you every knob RDS exposes, which is exactly what you want when a platform team is tuning a shared database fleet. The Encore version is derived from the code that uses the database, runs the same locally with encore run and in production, and has no separate state file to manage. Which is better depends entirely on which of the two groups above you're in.
Encore Cloud provisions the underlying AWS or GCP services in your own cloud account. There are no runtime dependencies on Encore in production, the framework is open source, and encore build docker produces a standalone image for teams that later decide they want raw control.
This is not a replacement for Terraform in the cases where you need it. Teams running large VM fleets, configuring networking and IAM at scale, or operating multi-cloud regulated infrastructure will keep using IaC, and should. What infrastructure from code replaces is the configuration sprawl that accretes around a single application's backend, which is precisely the situation where Terraform tends to be overhead.
Terraform is not dying and it is not overkill for everyone. For multi-cloud estates, platform teams, regulated environments, and existing large deployments, it's the right tool and the setup cost is justified. For a small team on one cloud building something new, where the infrastructure is a database and a queue in service of one app, it's often a workflow you'll maintain for less benefit than the effort costs.
The mistake is treating Terraform as the automatic default without checking which group you're in. Run the checklist. If you're clearly in the "need it" column, adopt it and don't look back. If you're clearly in the "skip it" column, a platform or an infrastructure-from-code framework will get you further, faster.
Skip the IaC layer. Deploy a TypeScript backend with Postgres and cron to your own AWS or GCP account in minutes.
Usually not. For a single-cloud app run by one or two engineers, Terraform's state files, backends, and module structure often cost more time than they save. A platform or an infrastructure-from-code framework will provision what you need with far less setup.
Yes, if you work on teams that manage infrastructure at scale or across multiple clouds. Terraform (and OpenTofu) remain the default for large estates, and knowing HCL is a widely transferable skill. For greenfield single-cloud apps, the skill is less essential than it was five years ago.
Depends on the job. Pulumi and AWS CDK if you want a general-purpose language. OpenTofu if you want an MPL-licensed Terraform. An infrastructure-from-code framework like Encore if the infrastructure is application-level (databases, queues, cron, storage) and you want it derived from your code. Cloud consoles or the CLI for genuinely one-off resources.
Many teams do. Managed platforms, PaaS products, and infrastructure-from-code frameworks provision and manage real cloud resources without you writing any HCL. The question is whether your infrastructure fits the application-layer model those tools cover, or whether you need the full breadth of raw cloud resources Terraform can address.
When you're managing infrastructure that spans clouds, has strict compliance and audit requirements, is operated by a dedicated platform team, or already exists as a large Terraform estate. At that scale the explicit state and plan/apply model earns its keep instead of getting in the way.