Pulumi is an infrastructure-as-code platform that ships v3.236.0 (May 7, 2026) under the Apache 2.0 licence and lets you define cloud resources in TypeScript, Python, Go, C#, Java, or YAML instead of HCL. The story in 2026 is licensing and language: HashiCorp moved Terraform to the Business Source License in August 2023, the Linux Foundation forked it as OpenTofu a week later under MPL 2.0, and Pulumi sat the whole licensing fight out under Apache 2.0. Three years on, the choice between the three is no longer just about syntax — it is about whether your IaC code is ordinary TypeScript you can unit test, or a domain-specific language with its own ecosystem.
This page is the primer. It covers what Pulumi actually is, how it lines up against Terraform and OpenTofu in 2026, the install in under a minute, your first AWS S3 stack in TypeScript, where the state actually lives, and what Pulumi can do that the HCL camp cannot. The companion read for CI is the GitHub Actions guide — the env.dev site itself runs Pulumi from a GitHub Actions workflow against Cloudflare.
TL;DR
- Pulumi 3.236 (Apache 2.0) writes IaC in TypeScript, Python, Go, C#, Java, or YAML. Same providers as Terraform — different runtime.
- Three licences, one workflow: Pulumi (Apache 2.0), OpenTofu 1.11 (MPL 2.0, Linux Foundation), Terraform 1.x (BSL 1.1 since Aug 2023). All three model state, plan, and apply.
- Ecosystem gap closed in 2026: Pulumi Cloud now serves as a state backend for Terraform and OpenTofu, and the Pulumi CLI runs HCL natively through a Terraform bridge.
- Install:
curl -fsSL https://get.pulumi.com | sh(orbrew install pulumi). First stack:pulumi new aws-typescript→pulumi up. Ten minutes from zero to a deployed S3 bucket. - Pick Pulumi when you want unit-testable infra, real loops and functions, or an Apache 2.0 licence. Pick OpenTofu when you want HCL without BSL. Stay on Terraform when you need the absolute biggest hiring pool and HashiCorp's commercial Terraform Cloud.
What Is Pulumi?
Pulumi is an open-source IaC engine plus a set of language SDKs. You write a normal program — an index.ts, a __main__.py, a main.go — that declares resources by constructing them. The Pulumi CLI executes that program, builds a resource graph, diffs it against the last known state, and calls the cloud provider APIs to make the world match. There is no separate planning language. The same TypeScript that builds your app builds the bucket the app reads from.
The core engine is Apache 2.0 and lives at github.com/pulumi/pulumi. Pulumi Cloud is the optional managed service — free for individuals, paid for teams — that holds state, runs deployments, and stores secrets. You can run Pulumi entirely without it: state goes to S3, Azure Blob, GCS, Postgres, or a local file.
The five things every Pulumi program has
- Project — a directory with a
Pulumi.yamlthat names the runtime (TypeScript, Python, etc.). - Stack — an isolated instance of the project (
dev,staging,prod). Each stack has its own state and config. - Resources — typed objects from a provider package (
@pulumi/aws,@pulumi/cloudflare) constructed inside your program. - Inputs and Outputs — async, dependency-tracked values that wire resources together (a bucket name flowing into a CloudFront origin, for example).
- Backend — Pulumi Cloud or DIY (S3, GCS, local). Holds state, locks, and history.
Pulumi vs Terraform vs OpenTofu: How Do They Compare?
All three deploy the same clouds and largely the same providers. The differences are language, licensing, testing, and ecosystem maturity. The table is the snapshot in May 2026 — versions, stars, and counts come from each project's release pages and registries.
| Dimension | Pulumi | Terraform | OpenTofu |
|---|---|---|---|
| Latest version | v3.236.0 (May 2026) | v1.13 series (2026) | v1.11.6 (Apr 2026) |
| Licence | Apache 2.0 (open source) | BSL 1.1 (source-available) | MPL 2.0 (open source) |
| Steward | Pulumi Corp | HashiCorp / IBM | Linux Foundation |
| Language | TS, Py, Go, C#, Java, YAML | HCL | HCL |
| State by default | Pulumi Cloud (free for individuals) | Local file or Terraform Cloud | Local file (or third-party SaaS) |
| Native testing | Yes — pytest, Jest, Go test, xUnit | No (use Terratest) | No (use Terratest) |
| Provider count | ~150 first-party + Terraform bridge | 4,800+ in the registry | ~3,500+ (community-sourced) |
| Native HCL support | Yes (Jan 2026, via TF bridge) | Yes (it is HCL) | Yes (it is HCL) |
| State encryption | At rest (Pulumi Cloud) + per-secret | At rest only (depends on backend) | Built-in client-side encryption |
| Best at | Software-engineering-shaped infra | Largest hiring pool, biggest registry | HCL without BSL strings attached |
The official side-by-side from each vendor sits at Pulumi vs Terraform and OpenTofu vs Terraform. Read both — the framing is opinionated but the technical facts hold up.
Why Does Apache 2.0 Matter in 2026?
For an end-user team running Terraform against AWS, BSL 1.1 is mostly invisible — you can keep using Terraform for free to manage your own infrastructure. The licence only bites if you build a competing product on top of Terraform's source code. So why does anyone care?
- Tool builders cannot ship. Spacelift, env0, Scalr, and Gruntwork all sell Terraform-adjacent products. BSL gave them a choice: rewrite, fork, or pay HashiCorp. Within a week they announced the OpenTofu fork; the Linux Foundation took stewardship five months later.
- The fork is now diverging. OpenTofu 1.7 added client-side state encryption Terraform does not have. 1.8 added
for_eachon provider blocks. 1.11 keeps adding features that will never backport. By 2027 they are different products. - IBM bought HashiCorp in February 2025, which closed the door on a quick BSL reversal. Whatever you think of IBM, the licence is unlikely to soften.
- Pulumi sat it out. Apache 2.0 from day one, no relicensing, no fork drama. For platform teams shipping internal developer platforms, that stability is the actual deliverable.
If you have an existing Terraform 1.5.x codebase and just want the licensing problem to go away, the OpenTofu guide walks the binary-swap migration end to end — same HCL, same providers, same workflow. If you are starting fresh and have a team that already lives in TypeScript or Python, Pulumi removes a whole language from your stack.
How Do You Install Pulumi?
Three commands and you are done. The CLI is a single static binary; everything else is downloaded on demand.
# macOS / Linux
curl -fsSL https://get.pulumi.com | sh
# macOS (Homebrew)
brew install pulumi/tap/pulumi
# Windows (Scoop)
scoop install pulumi
# Verify
pulumi version
# v3.236.0The CLI talks to a backend. For the first stack, point it at Pulumi Cloud — free, no credit card, browser-based sign-in. We will swap to a self-managed S3 backend later in the page.
# Default — Pulumi Cloud (recommended for the first run)
pulumi login
# Or DIY — self-managed state in S3
pulumi login 's3://my-pulumi-state?region=us-east-1&awssdk=v2'
# Or fully local — JSON file under ~/.pulumi/
pulumi login --localYour First Stack: AWS S3 in TypeScript
The fastest path is the aws-typescript template. It scaffolds a project, initialises a stack, and gives you an index.ts ready to edit. AWS credentials come from the standard chain — ~/.aws/credentials, env vars, IAM Identity Center, whatever the SDK already finds.
mkdir my-first-stack && cd my-first-stack
pulumi new aws-typescript --yes \
--name my-first-stack \
--description "First Pulumi stack" \
--stack dev
# Set the AWS region for this stack (config is per-stack)
pulumi config set aws:region us-east-1
# Preview, then apply
pulumi preview
pulumi up --yesThe scaffolded program is just TypeScript. Open index.ts and you will see something close to this — a single resource, a single export. Add tags, a versioning block, a public-access block, and you have a usable bucket.
import * as aws from '@pulumi/aws';
import * as pulumi from '@pulumi/pulumi';
const config = new pulumi.Config();
const env = pulumi.getStack(); // 'dev', 'staging', 'prod'
const bucket = new aws.s3.BucketV2('app-assets', {
bucket: `app-assets-${env}`,
tags: { Environment: env, ManagedBy: 'pulumi' },
});
new aws.s3.BucketVersioningV2('app-assets-versioning', {
bucket: bucket.id,
versioningConfiguration: { status: 'Enabled' },
});
new aws.s3.BucketPublicAccessBlock('app-assets-pab', {
bucket: bucket.id,
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicPolicy: true,
});
export const bucketName = bucket.id;
export const bucketArn = bucket.arn;Run pulumi up again and the diff is real — three resources to create, zero to update, zero to delete. After it returns, pulumi stack output prints the exported names, and pulumi destroy tears the whole stack down. Because env comes from getStack(), the same code runs as staging the moment you create that stack — pulumi stack init staging and pulumi up.
The same thing in HCL
For comparison, the Terraform / OpenTofu equivalent — same three resources, no getStack() magic because HCL has no functions, just templated names and a workspace.
variable "env" { type = string }
resource "aws_s3_bucket" "app_assets" {
bucket = "app-assets-${var.env}"
tags = { Environment = var.env, ManagedBy = "terraform" }
}
resource "aws_s3_bucket_versioning" "app_assets" {
bucket = aws_s3_bucket.app_assets.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_public_access_block" "app_assets" {
bucket = aws_s3_bucket.app_assets.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_policy = true
}
output "bucket_name" { value = aws_s3_bucket.app_assets.id }At this size the two are roughly equal in line count. The gap opens up when you need a loop over a list of accounts, conditional creation based on a feature flag, or a helper function that derives a tag set from the stack name. In TypeScript that is a .map() and an if; in HCL it is for_each, count, dynamic blocks, and a careful read of the Meta-Arguments section of the docs.
Where Does Pulumi Store State?
State is the JSON record of every resource Pulumi has created and the metadata it needs to update or delete it. Lose it and Pulumi cannot reconcile reality — every pulumi up will try to recreate things that already exist. Three options, in order of how much you offload.
Pulumi Cloud (default)
Free for individuals; $40/month for teams. State, locks, history, audit log, RBAC, secrets, drift detection, review stacks. The path of least resistance.
pulumi loginDIY object storage
S3, Azure Blob, GCS, Minio, or Postgres. $0 in Pulumi fees. You own backups, IAM, and — critically — your own locking story. State locking with DynamoDB is a common add-on.
pulumi login s3://...Local file
JSON under ~/.pulumi/. For demos and personal projects only — no concurrency control, no history, no team. Lose your laptop, lose your stack.
pulumi login --localOne non-obvious 2026 footnote: Pulumi Cloud is now also a state backend for Terraform and OpenTofu. If your team runs both, you can centralise state in one place without rewriting any HCL. The full backend matrix lives in the State and Backends docs.
What Can Pulumi Do That HCL Cannot?
Most of the gap is what you would expect from a real programming language: loops, functions, classes, npm/pip packages. The four features below are the ones most often cited as decisive.
Native unit tests
Mock the cloud provider with the Pulumi runtime, then assert on resource properties using Jest, pytest, Go test, or xUnit. No external Terratest harness, no real cloud spin-up. Catches a bad tag or a forgotten encryption flag in milliseconds.
pulumi.runtime.setMocks(...)Automation API
Embed the Pulumi engine inside your application — every CLI command exposed as a typed function. Self-serve developer portals, per-tenant SaaS infrastructure, custom IaC platforms. Available in TS, Py, Go, C#, Java.
await stack.up()Component resources
Bundle a set of low-level resources behind a typed class with its own props and outputs — the Pulumi equivalent of a Terraform module, but you get inheritance, generics, and IDE jump-to-definition for free.
extends pulumi.ComponentResourceReview Stacks
Open a PR, Pulumi Cloud spins up an ephemeral stack with the change applied, posts the outputs as a PR comment, and tears it down on merge. Zero-trust gated preview environments without a custom CI pipeline.
previewPullRequests: trueOpenTofu has been catching up on infrastructure-side wins — built-in state encryption, for_each on providers, early variable evaluation. But the four bullets above are language-shaped, and HCL is not going to grow a class system.
When Should You Not Use Pulumi?
Honest list. Pulumi is not the right answer everywhere.
- Your team has zero programmers. If the people who manage infra are SREs or network engineers who do not write application code, HCL is the lower-floor option. A bad TypeScript program can do things a bad
.tffile simply cannot — like loop forever or hit the network during a plan. - You need a niche provider that only Terraform has. The Pulumi registry covers about 150 first-party providers; Terraform's registry has 4,800+. The Pulumi Terraform bridge usually closes the gap, but if you are deploying obscure on-prem hardware, check first.
- You hire on "Terraform experience." The hiring pool for HCL is several times larger than for Pulumi (LinkedIn talent insights). For a team optimising for replaceability over feature breadth, HCL is the safer choice.
- You want a fully air-gapped, no-vendor-saas setup, with locking. Pulumi Cloud is the easiest path to safe team collaboration. The DIY S3 backend works but you build the locking yourself (DynamoDB or external lock). OpenTofu has the same gap; Terraform Cloud / Spacelift / env0 fill it commercially.
- A single-resource demo. If all you need is to provision one EC2 instance, the AWS console is faster. IaC pays off when the stack has 20+ resources or when you are deploying the same shape into multiple environments.
How Does Pulumi Handle CI/CD?
Pulumi runs cleanly in any CI runner. The standard pattern: install the CLI, authenticate to the backend, pulumi preview on PRs, pulumi up on merges to main. The official pulumi/actions bundles the install and the command runner.
name: Infra
on:
pull_request: { branches: [main] }
push: { branches: [main] }
permissions:
contents: read
id-token: write # for AWS OIDC, no static keys
jobs:
preview:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/pulumi-ci
aws-region: us-east-1
- uses: pulumi/actions@v6
with:
command: preview
stack-name: dev
comment-on-pr: true
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}Authenticate to your cloud the same way you would for a Terraform job — OIDC for AWS / GCP / Azure beats static secrets every time. For the secrets side, see the GitHub Actions secrets and env vars guide for the right place to put PULUMI_ACCESS_TOKEN.
Frequently Asked Questions
Is Pulumi free?
The CLI, language SDKs, and providers are Apache 2.0 — free forever, no usage caps, no licensing. Pulumi Cloud (the managed state and deployment service) is free for individuals and starts at $40/month for teams. You can run Pulumi without Pulumi Cloud entirely by storing state in S3, Azure Blob, GCS, Postgres, or a local file.
Can Pulumi import existing Terraform code?
Yes. pulumi convert --from terraform translates HCL to your chosen Pulumi language (TypeScript, Python, Go, C#). As of January 2026, the Pulumi CLI also runs HCL natively through a Terraform bridge, so you can mix HCL modules and Pulumi components in the same project without rewriting. Pulumi Cloud now also serves as a state backend for Terraform and OpenTofu projects.
Pulumi vs Terraform: which one should I pick in 2026?
Pick Pulumi if your team writes TypeScript, Python, Go, or C# day-to-day, you want unit-testable infrastructure, or Apache 2.0 licensing matters to you. Pick Terraform if you need the largest hiring pool, the biggest provider registry (4,800+), or the commercial Terraform Cloud features. Pick OpenTofu if you want HCL without the BSL license — same syntax, same providers, MPL 2.0, Linux Foundation governance.
What languages does Pulumi support?
TypeScript and JavaScript (Node.js LTS or Bun 3.227+), Python 3.10–3.14, Go, .NET (C# and F#), Java, and YAML. TypeScript is the most documented and the default recommendation for new teams — strong typing, IDE autocomplete, and the largest community example base.
Where does Pulumi store state?
In a backend you choose at pulumi login. Pulumi Cloud is the default — free for individuals, with state locking, history, RBAC, audit logs, and secrets encryption built in. The DIY alternatives are S3, Azure Blob Storage, Google Cloud Storage, S3-compatible servers like Minio or Ceph, Postgres, or a local JSON file under ~/.pulumi/. DIY backends do not include built-in locking — you have to wire up DynamoDB or another lock store yourself.
How do I migrate from Terraform to Pulumi?
Two paths. (1) Convert: run pulumi convert --from terraform on each module to translate HCL to TypeScript/Python/Go/C#. (2) Coexist: as of 2026, Pulumi runs HCL natively and can read Terraform/OpenTofu state, so you can move resources stack-by-stack without rewriting. Most teams convert the parts they want to extend with real code (loops, conditionals, components) and leave stable HCL in place.
Does Pulumi work without internet access?
The CLI and language SDKs run offline once installed and once your provider plugins are cached. The default Pulumi Cloud backend obviously needs network access. For air-gapped environments, log into a self-hosted Pulumi Cloud instance, an S3-compatible object store, or a local-file backend. Provider plugins must be pre-fetched into the local plugin cache.
References
- Pulumi: Get Started — official quick-start with AWS, Azure, GCP, and Kubernetes templates.
- Pulumi vs Terraform — official comparison — the vendor view; useful even with the framing taken into account.
- OpenTofu vs Terraform — Pulumi's comparison — feature divergence between OpenTofu 1.11 and Terraform 1.x.
- State and Backends — Pulumi docs — backend matrix: Pulumi Cloud, S3, Azure, GCS, Postgres, local file.
- Automation API — Pulumi docs — embedding Pulumi inside your own application across six languages.
- TypeScript and Node.js SDK — Pulumi docs — the runtime story for Node.js LTS and Bun.
- github.com/pulumi/pulumi — source repo, Apache 2.0, 25k+ stars, current release v3.236.0.
- OpenTofu — the Linux Foundation fork of Terraform 1.6 (latest 1.11.6, MPL 2.0).
- github.com/opentofu/opentofu — OpenTofu source, 28k+ stars.
- HashiCorp adopts the Business Source License (Aug 2023) — the original announcement that sparked the OpenTofu fork.
Pulumi pairs naturally with the rest of the IaC stack. Compare the variable model with the Terraform variables guide if you are weighing the migration, and read the GitHub Actions guide for wiring pulumi up into your pipeline.