The same infrastructure written three ways, and what I learned.
Infrastructure as code has split into two camps. One says infrastructure should be described in a declarative configuration language, like Terraform's HCL. The other says: we already have great programming languages, why not use them? That's the idea behind Pulumi and the AWS CDK.
The best way to compare them is to write the same thing in all three. So here's a simple S3 bucket with versioning, in each.
Terraform (HCL)
resource "aws_s3_bucket" "assets" {
bucket = "acme-assets-prod"
tags = { environment = "production" }
}
resource "aws_s3_bucket_versioning" "assets" {
bucket = aws_s3_bucket.assets.id
versioning_configuration {
status = "Enabled"
}
}
output "bucket_name" {
value = aws_s3_bucket.assets.bucket
}
Pulumi (TypeScript)
import * as aws from "@pulumi/aws";
const assets = new aws.s3.BucketV2("assets", {
bucket: "acme-assets-prod",
tags: { environment: "production" },
});
new aws.s3.BucketVersioningV2("assets", {
bucket: assets.id,
versioningConfiguration: { status: "Enabled" },
});
export const bucketName = assets.bucket;
AWS CDK (TypeScript)
import { Stack, StackProps, Tags, CfnOutput } from "aws-cdk-lib";
import * as s3 from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";
export class AssetsStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const assets = new s3.Bucket(this, "Assets", {
bucketName: "acme-assets-prod",
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});
Tags.of(assets).add("environment", "production");
new CfnOutput(this, "BucketName", { value: assets.bucketName });
}
}
HCL vs real languages
The examples look similar for a single bucket. The differences show up as things grow.
HCL (Terraform, OpenTofu) is deliberately limited. Loops and conditions exist (for_each, count, ternaries), but it's not a full programming language. That's a feature: configurations are easy to read, even for people who aren't developers, and there's less room for clever code nobody understands.
Pulumi lets you use TypeScript, Python, Go, C#, Java or YAML. You get real loops, functions, classes, packages, IDE autocompletion and unit tests with familiar frameworks. Great for complex logic, and for developers who don't want to learn another language. The risk: infrastructure code that's too clever.
AWS CDK also uses real languages, but its strength is constructs: high-level building blocks with good defaults. Notice how the CDK bucket above got encryption and blocked public access almost for free. Higher-level constructs can create a whole load-balanced container service in a few lines. It generates CloudFormation underneath and is AWS only.
HCL limits what you can write. Real languages limit nothing, which is both their strength and their danger.
State, compared
- Terraform/OpenTofu: state file in a backend you manage (S3, GCS, Azure Storage) or in HCP Terraform and other platforms.
- Pulumi: state in Pulumi Cloud by default, or self-managed in an object storage bucket.
- CDK: no separate state file. CloudFormation tracks the stack's state in AWS. Simpler, but you inherit CloudFormation's speed and limitations.
What fits which team
Choose Terraform or OpenTofu if:
- You work across multiple clouds and SaaS providers (the provider ecosystem is the largest).
- Your team includes ops people who aren't primarily developers.
- You want the most widely known tool, with the most examples and hiring options.
Choose Pulumi if:
- Your team is developer-heavy and prefers TypeScript, Python or Go.
- Your infrastructure has complex logic that's painful in HCL.
- You want to test infrastructure code with normal unit testing tools.
Choose AWS CDK if:
- You're all-in on AWS and plan to stay there.
- You like high-level constructs with good defaults.
- Your team already knows CloudFormation, or is happy to rely on it.
What I learned
Writing the same infrastructure three times made one thing clear: the language matters less than the discipline. Good module or component design, small state boundaries, consistent naming, tests and reviews matter in all three.
For most teams I work with, Terraform or OpenTofu remains the default because of its ecosystem and readability. But for developer-led teams, I've seen Pulumi make infrastructure feel much more approachable.
My take: pick the tool your team will read and maintain happily. Infrastructure code is read far more often than it's written.
— Desly
Originally published on deslypeter.com.
Top comments (0)