Terraform uses HCL. AWS CDK is verbose. Pulumi lets you write infrastructure in TypeScript, Python, Go, or C# — languages you already know.
Quick Start
curl -fsSL https://get.pulumi.com | sh
pulumi new aws-typescript
Your First Infrastructure
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Create an S3 bucket
const bucket = new aws.s3.Bucket("my-bucket", {
website: {
indexDocument: "index.html",
},
});
// Upload a file
const indexHtml = new aws.s3.BucketObject("index", {
bucket: bucket.id,
content: "<h1>Hello from Pulumi!</h1>",
contentType: "text/html",
});
// Export the URL
export const url = pulumi.interpolate`http://${bucket.websiteEndpoint}`;
pulumi up # Preview and deploy
Real-World: Full-Stack on AWS
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
// VPC with best practices
const vpc = new awsx.ec2.Vpc("app-vpc", { natGateways: { strategy: "Single" } });
// RDS Database
const db = new aws.rds.Instance("app-db", {
engine: "postgres",
instanceClass: "db.t4g.micro",
allocatedStorage: 20,
dbName: "appdb",
username: "admin",
password: config.requireSecret("dbPassword"),
vpcSecurityGroupIds: [dbSg.id],
dbSubnetGroupName: dbSubnetGroup.name,
skipFinalSnapshot: true,
});
// ECS Fargate Service
const service = new awsx.ecs.FargateService("app", {
cluster: cluster.arn,
taskDefinitionArgs: {
container: {
name: "app",
image: "my-app:latest",
cpu: 256,
memory: 512,
portMappings: [{ containerPort: 3000 }],
environment: [
{ name: "DATABASE_URL", value: pulumi.interpolate`postgres://admin:${dbPassword}@${db.endpoint}/appdb` },
],
},
},
});
// Application Load Balancer
const alb = new awsx.lb.ApplicationLoadBalancer("app-lb");
Pulumi vs Terraform
| Feature | Pulumi | Terraform |
|---|---|---|
| Language | TS, Python, Go, C# | HCL |
| Loops/Conditions | Native | count/for_each |
| Testing | Unit tests | Limited |
| IDE Support | Full autocomplete | HCL plugins |
| State | Pulumi Cloud or S3 | Terraform Cloud or S3 |
| Reusable Code | Functions/classes | Modules |
| Learning Curve | Low (if you know the language) | Medium |
Testing Infrastructure
import * as pulumi from "@pulumi/pulumi";
import { describe, it, expect } from "vitest";
describe("infrastructure", () => {
it("bucket should have versioning", async () => {
const bucket = new aws.s3.Bucket("test", {
versioning: { enabled: true },
});
const versioning = await new Promise(resolve =>
bucket.versioning.apply(v => resolve(v))
);
expect(versioning.enabled).toBe(true);
});
});
Secrets Management
# Set a secret (encrypted in state)
pulumi config set --secret dbPassword "super-secret-123"
const config = new pulumi.Config();
const dbPassword = config.requireSecret("dbPassword");
// dbPassword is automatically encrypted in state
Need cloud infrastructure for scraping? Check out my Apify actors — managed scraping infrastructure, no DevOps needed. For custom solutions, email spinov001@gmail.com.
Top comments (0)