If you have decided to manage infrastructure as code and narrowed the field to Terraform and Pulumi, the real question is narrow: do you want infrastructure described in a declarative configuration language, or written in the same programming language as the rest of your stack? Almost everything else follows from that one choice.
This comparison covers how each tool models infrastructure, what the code looks like, how state and pricing work, and where the OpenTofu fork lands in 2026. It ends with guidance on when each tool is the better pick, and a short note on when neither is what you want.
Terraform uses HashiCorp Configuration Language (HCL), a declarative DSL built for describing infrastructure. Pulumi uses general-purpose languages: TypeScript, JavaScript, Python, Go, C#, Java, and YAML. You write infrastructure the same way you write application code, with loops, functions, classes, and your normal package manager.
Both are declarative in outcome. Pulumi code runs to build a resource graph, then Pulumi diffs that graph against state and applies the delta, the same reconcile model Terraform uses. The difference is the authoring experience, not the execution model.
| Terraform / OpenTofu | Pulumi | |
|---|---|---|
| Language | HCL (declarative DSL) | TypeScript, Python, Go, C#, Java, YAML |
| Provider ecosystem | Largest; native registry | Bridges Terraform providers + native providers |
| State backend | S3, GCS, Azure Blob, HCP Terraform, local | Pulumi Cloud, S3, GCS, Azure Blob, local |
| Secrets | Marked sensitive; external KMS for encryption | Built-in encrypted secrets in state |
| Testing | terraform validate, plan review, Terratest | Native unit tests in your language + policy |
| License | BSL (Terraform) / MPL (OpenTofu) | Apache 2.0 |
| Hiring pool | Very large | Smaller but growing |
| Learning curve | Learn HCL + provider resources | Learn Pulumi SDK; language already known |
Take the same task in both tools: an AWS S3 bucket, a DynamoDB table, and three environment-specific buckets created in a loop.
Terraform (HCL):
variable "environments" {
type = list(string)
default = ["dev", "staging", "prod"]
}
resource "aws_s3_bucket" "assets" {
bucket = "myapp-assets"
}
resource "aws_dynamodb_table" "sessions" {
name = "sessions"
billing_mode = "PAY_PER_REQUEST"
hash_key = "id"
attribute {
name = "id"
type = "S"
}
}
resource "aws_s3_bucket" "env" {
for_each = toset(var.environments)
bucket = "myapp-${each.value}"
}
Pulumi (TypeScript):
import * as aws from "@pulumi/aws";
const environments = ["dev", "staging", "prod"];
const assets = new aws.s3.Bucket("assets", {
bucket: "myapp-assets",
});
const sessions = new aws.dynamodb.Table("sessions", {
billingMode: "PAY_PER_REQUEST",
hashKey: "id",
attributes: [{ name: "id", type: "S" }],
});
const envBuckets = environments.map(
(env) => new aws.s3.Bucket(`env-${env}`, { bucket: `myapp-${env}` }),
);
The HCL is more constrained and easier to read cold, and for_each handles the loop declaratively. The TypeScript is a plain array map, which is second nature to a JavaScript developer and composes with any function or npm package you already have. Neither is objectively cleaner. HCL keeps everyone in the same narrow language; Pulumi lets a strong programmer abstract aggressively, for better or worse.
Both tools keep a state file that maps your declarations to real cloud resources, and both reconcile against it on every apply. That means both share the same failure modes: drift when someone edits a resource by hand, lock contention when two applies run at once, and the need to store state somewhere durable and shared.
Terraform and OpenTofu store state in a backend you configure: S3 with DynamoDB locking, GCS, Azure Blob, or HCP Terraform. OpenTofu added native state encryption at the file level, which Terraform still leaves to the backend (for example S3 server-side encryption).
Pulumi stores state in Pulumi Cloud by default, and encrypts secrets inside that state out of the box. You are not locked into the hosted service: pulumi login s3://my-bucket (or gs://, azblob://, or a local path) moves state to a backend you own, at no cost.
The practical upshot: secrets handling is nicer in Pulumi by default, and self-managed state is a one-line change in both. Neither tool escapes the fundamentals of stateful reconciliation.
Terraform the CLI is free (BSL-licensed). OpenTofu the CLI is free (MPL-licensed). The paid product is HCP Terraform (formerly Terraform Cloud), which bills for managed runs, state, and policy, with a free tier for small teams.
Pulumi the CLI and SDKs are free (Apache 2.0). Pulumi Cloud is the paid product, free for individuals and billed per resource under management above the free tier. As with Terraform, you can avoid the paid tier entirely by self-managing state.
For a team that self-manages state and runs applies in its own CI, both tools cost nothing in licensing. The bill only appears if you adopt the vendor's managed control plane.
In August 2023, HashiCorp relicensed Terraform from MPL to the Business Source License. A coalition forked the last MPL release into OpenTofu, now under the Linux Foundation, and in 2025 IBM acquired HashiCorp. For most end users running terraform apply against their own infrastructure, the BSL changes nothing. For vendors building products on top of Terraform, and for teams with a policy preference for OSI-approved licenses, it changed a lot.
This matters to the Pulumi comparison because "we do not want to be on HashiCorp's license terms" used to be a reason teams evaluated Pulumi. In 2026 that reason mostly points at OpenTofu instead, since OpenTofu keeps HCL, the state format, and the provider ecosystem while being permissively licensed. If your objection to Terraform was legal rather than ergonomic, you probably want OpenTofu, not Pulumi. If your objection was HCL itself, Pulumi is still the answer.
pulumi convert) or want to reuse Terraform providers from a programming language.A caveat on Pulumi: general-purpose languages let you build clever abstractions that are hard for the next engineer to follow. HCL's rigidity is a feature when the goal is config that anyone on the team can read a year later. Choose Pulumi for the power, and set conventions so you do not pay for it in review.
Both tools assume infrastructure lives in a separate codebase, a separate language, and a separate state file, with the application code unaware of any of it. That split fits VMs, networking, and IAM managed by a platform team. It fits less well when the "infrastructure" is application-level: a Postgres database, a Pub/Sub topic, a cron job, an object storage bucket that one service reads and writes.
For that case, infrastructure-from-code frameworks declare those resources in the application code itself. Encore is the open-source example for TypeScript and Go, with over 12,000 GitHub stars and production users including Groupon:
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { Topic } from "encore.dev/pubsub";
// Provisions managed Postgres (RDS on AWS, Cloud SQL on GCP, Docker locally).
const db = new SQLDatabase("orders", { migrations: "./migrations" });
// Provisions Pub/Sub (SNS+SQS on AWS, Pub/Sub on GCP, NSQ locally).
export const orderPlaced = new Topic<{ orderID: string }>("order-placed", {
deliveryGuarantee: "at-least-once",
});
Encore Cloud provisions the real AWS or GCP services in your own cloud account, with no separate Terraform or Pulumi module and no separate state file to reconcile. This does not replace Terraform or Pulumi for large VM fleets or regulated networking; it removes the config sprawl that grows around an application backend. For a new backend where the choice is not constrained by an existing IaC investment, it is worth a look before you commit to either tool.
Terraform and Pulumi are both solid in 2026, and the choice is not about quality. Pick Terraform or OpenTofu for the widest ecosystem, the largest talent pool, and a declarative DSL that resists over-engineering, with OpenTofu answering the license concern. Pick Pulumi if you want infrastructure in your team's own programming language with real abstraction and built-in secret encryption. And if you are standing up a new backend whose infrastructure is mostly application-level, check whether an infrastructure-from-code approach lets you skip the separate IaC layer entirely.
Skip the separate IaC layer. Deploy a TypeScript backend with Postgres and Pub/Sub to your own AWS or GCP account in minutes.
Neither is strictly better. Terraform (and its OpenTofu fork) has the larger provider ecosystem and the bigger hiring pool, and it stays declarative in HCL. Pulumi lets you define infrastructure in TypeScript, Go, Python, or C#, which suits teams that want loops, functions, and their existing test tooling. Pick Terraform for breadth and portability, Pulumi for programming-language ergonomics.
No. Pulumi defaults to the managed Pulumi Cloud for state, but you can point it at S3, GCS, Azure Blob, or a local file with `pulumi login`. The self-managed backends are free. Pulumi Cloud has a free tier for individuals and charges per resource above it.
OpenTofu is the MPL-licensed, Linux Foundation fork of Terraform created after HashiCorp moved Terraform to the BSL license. It is a drop-in replacement for most Terraform workflows and shares HCL and the state format. If your objection to Terraform was the BSL license, OpenTofu removes it, which weakens one of the historical reasons teams looked at Pulumi.
Yes. Pulumi bridges the Terraform provider ecosystem, so most Terraform providers are available in Pulumi, and Pulumi also converts existing HCL with `pulumi convert`. Provider coverage is rarely the deciding factor between the two.
Yes. Both track provisioned resources in state and reconcile against it on every apply. Terraform and OpenTofu store state in a backend such as S3 or HCP Terraform. Pulumi stores state in Pulumi Cloud or a self-managed backend. The concept, and its failure modes like drift and lock contention, is the same in both.
For a brand-new backend where the infrastructure is mostly application-level (a database, a queue, cron, object storage), it is worth asking whether you need either tool. Infrastructure-from-code frameworks like Encore let you declare those resources in your application code and provision them without a separate IaC repository. For large VM fleets, networking, and IAM at scale, Terraform or Pulumi is still the right layer.