Terraform uses HCL, a domain-specific language designed for infrastructure. Pulumi lets you write infrastructure in TypeScript, Python, Go, C#, or Java. The core trade-off: HCLβs simplicity and ecosystem vs general-purpose language power.
Side-by-Side: Same Infrastructure, Different Languages
Terraform (HCL):
resource "aws_s3_bucket" "data" {
bucket = "mycompany-data-${var.environment}"
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}Pulumi (TypeScript):
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("data", {
bucket: `mycompany-data-${environment}`,
tags: {
Environment: environment,
ManagedBy: "pulumi",
},
});
new aws.s3.BucketVersioningV2("data-versioning", {
bucket: bucket.id,
versioningConfiguration: { status: "Enabled" },
});Both produce the same infrastructure. The difference is what happens when complexity grows.
Feature Comparison
| Capability | Terraform | Pulumi |
|---|---|---|
| Language | HCL (domain-specific) | TypeScript, Python, Go, C#, Java |
| Type safety | Limited (variable validation) | Full compile-time type checking |
| IDE support | Good (HCL plugins) | Excellent (full IntelliSense, refactoring) |
| Testing | terraform test, Terratest | Standard unit test frameworks (Jest, pytest) |
| Loops/conditionals | for_each, count, ternary | Native loops, if/else, functions |
| State backend | S3, Terraform Cloud, PostgreSQL | Pulumi Cloud, S3, Azure Blob, local |
| Provider ecosystem | 3000+ providers (largest) | Same providers via Terraform bridge + native |
| Module reuse | Terraform Registry | Package managers (npm, pip, go modules) |
| Secrets | Marked sensitive, Vault | Built-in per-value encryption |
| Policy as code | Sentinel (paid), OPA | CrossGuard (built-in) |
| Learning curve | Learn HCL (1-2 weeks) | Use existing language skills |
| Community | Massive (10+ years) | Growing (6+ years) |
Where Pulumi Wins: Complex Logic
When infrastructure requires dynamic logic β conditionally creating resources, transforming data, calling APIs during provisioning β Pulumiβs general-purpose languages shine:
// Pulumi: Dynamic resource creation with real programming
const regions = ["us-east-1", "eu-west-1", "ap-southeast-1"];
const buckets = regions.map(region => {
const provider = new aws.Provider(`provider-${region}`, { region });
return new aws.s3.Bucket(`data-${region}`, {
bucket: `mycompany-data-${region}`,
tags: { Region: region },
}, { provider });
});
// Aggregate all bucket ARNs for IAM policy
const bucketArns = pulumi.all(buckets.map(b => b.arn));The equivalent in Terraform requires for_each with maps, which becomes unwieldy for complex scenarios.
Where Terraform Wins: Ecosystem and Simplicity
HCL is intentionally limited. This is a feature β infrastructure code should be declarative and readable by anyone, including junior engineers and platform consumers who do not write application code.
# Terraform: readable by anyone in 30 seconds
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "production"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
}Terraformβs module registry has thousands of production-tested modules. Pulumi can use the same providers (via a Terraform bridge) but the community modules are fewer.
State and Secrets
Both tools use state files, but Pulumi adds a critical feature: per-value secret encryption. Every secret value is encrypted in the state file by default.
// Pulumi: secrets are first-class
const dbPassword = new pulumi.Config().requireSecret("dbPassword");
// This value is encrypted at rest in the state fileTerraform marks values as sensitive to hide them from CLI output, but the state file contains them in plain text unless you encrypt the entire backend.
Decision Guide
Choose Terraform when:
- Your team manages infrastructure but does not write application code
- You need the largest provider and module ecosystem
- You want broad hiring pool (Terraform skills are more common)
- You are considering OpenTofu as a fork option
Choose Pulumi when:
- Your infrastructure team writes TypeScript/Python/Go daily
- You need complex conditional logic and transformations
- You want unit testing with standard test frameworks
- Per-value secret encryption is a compliance requirement
- You prefer package managers (npm/pip) over HCL modules