Pulumi is an infrastructure-as-code tool like Terraform, with one big difference: you write infrastructure in general-purpose programming languages, such as TypeScript, Python, Go, C# or Java, instead of HCL. Loops, functions, classes, packages and tests work the way you’re used to. Under the hood, Pulumi uses many of the same providers as Terraform.

Core concepts

Concept Meaning
Project A folder with Pulumi.yaml and your program
Stack An instance of the project (e.g. dev, prod), with its own config and state
Resources Objects your program declares (DNS records, buckets, servers)
State backend Where Pulumi stores state: Pulumi Cloud, or self-managed (S3-compatible, local files)
Config / secrets Per-stack settings; secrets are encrypted

Install and create a project

curl -fsSL https://get.pulumi.com | sh
pulumi version

mkdir infra && cd infra
pulumi login s3://pulumi-state?endpoint=s3.example-provider.com&s3ForcePathStyle=true   # self-managed backend
# or: pulumi login --local   (state in ~/.pulumi; personal use only)
pulumi new typescript        # or python, go, csharp…

Using a self-managed S3-compatible bucket (for example MinIO on your own server) keeps state under your control. See MinIO.

A small program (TypeScript)

Managing DNS records for several sites with the Cloudflare provider:

// index.ts
import * as cloudflare from "@pulumi/cloudflare";
import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();
const zoneId = config.require("zoneId");
const serverIp = config.require("serverIp");

const sites = ["blog", "shop", "status"];

export const records = sites.map(name =>
  new cloudflare.Record(name, {
    zoneId,
    name,
    type: "A",
    content: serverIp,
    proxied: true,
  }).hostname
);
npm install @pulumi/cloudflare
pulumi config set zoneId <zone-id>
pulumi config set serverIp 203.0.113.10
pulumi config set --secret cloudflare:apiToken <token>
pulumi preview         # like terraform plan
pulumi up              # apply (asks for confirmation)
pulumi stack output

Resource property names depend on the provider version. Check the provider’s API docs.

Stacks for environments

pulumi stack init staging
pulumi config set serverIp 203.0.113.20
pulumi up
pulumi stack select prod

Each stack has its own Pulumi.<stack>.yaml config, and separate state.

Secrets

pulumi config set --secret encrypts values in the stack config file (safe to commit). With self-managed backends, choose a secrets provider: a passphrase (PULUMI_CONFIG_PASSPHRASE), or a KMS/Vault key. Secret outputs stay encrypted in state. See secrets management.

Pulumi vs Terraform/OpenTofu

Pulumi Terraform / OpenTofu
Language TypeScript, Python, Go, C#, Java, YAML HCL
Abstractions Classes, functions, packages Modules
Testing Unit tests in your language’s test framework terraform test, Terratest
Ecosystem Many providers (some bridged from Terraform) Largest provider and module ecosystem
State Pulumi Cloud or self-managed backends Remote backends (S3, etc.)
Learning curve Easy if you know the language; programming discipline needed Simple declarative language

Choose Pulumi if your team prefers real programming languages, and wants to share libraries and tests. Choose Terraform/OpenTofu for the largest ecosystem and a simpler declarative style. See OpenTofu and Terraform modules.

Tips

  • Keep programs deterministic: avoid random values or API calls that change on every run (Pulumi has random resources for stable randomness).
  • Review pulumi preview output in CI before pulumi up. See CI/CD pipeline explained.
  • Use component resources to package reusable patterns (similar to Terraform modules).

Quick reference

Task Command
New project pulumi new typescript
Self-managed state pulumi login s3://bucket?endpoint=…
Config / secret pulumi config set [--secret] key value
Preview / apply pulumi preview / pulumi up
Environments pulumi stack init/select
Tear down pulumi destroy

Related: Terraform state and Ansible roles.

  • DevOps
  • Infrastructure as Code
  • Pulumi