Updated Nov 5, 20265 min read

Pulumi vs AWS CDK: Infrastructure as Code in TypeScript Compared

Both let you define cloud infrastructure in TypeScript. They differ on what runs the deploy, where state lives, which clouds they reach and what happens when a deploy fails.

Pulumi and AWS CDK both let a TypeScript team define infrastructure in the language it already uses, with loops, functions and types instead of YAML. Underneath, CDK compiles to CloudFormation and lets AWS run the deploy, while Pulumi runs its own engine against cloud APIs. That choice decides where state lives, how failed deploys recover, and whether resources outside AWS can live in the same program.

This comparison looks at both with the same example, a queue with a dead-letter queue, and then at Encore (infrastructure from code), which takes a different route: the infrastructure an application uses is declared in the application code itself.

Pulumi vs AWS CDK at a glance

EncoreAWS CDKPulumi
What you writeApplication code with infrastructure declarationsInfrastructure stacksInfrastructure programs
Deploy engineEncore platform, into your accountCloudFormationPulumi engine via provider APIs
StateManaged by the platformCloudFormation stacksPulumi Cloud or self-managed backend
CloudsAWS and GCPAWSAWS, GCP, Azure, Kubernetes and more
Failed deployFailed migrations abort the deployAutomatic rollbackPartial update recorded in state
LanguagesTypeScript, GoTypeScript, Python, Java, C#, GoTypeScript, Python, Go, C#, Java, YAML
Local environmentencore run with local infrastructureNoneNone

Each option is covered below, starting with Encore.

Declaring infrastructure in application code with Encore

Encore is infrastructure from code for TypeScript and Go, with a platform that provisions and deploys into your own AWS or GCP account. Instead of a separate stack that describes a queue, the service that publishes and consumes events declares them, and Encore creates SNS topics and SQS queues on AWS or Pub/Sub on GCP, each with a dead-letter queue and IAM permissions scoped to the services that use it.

Application code passes through Encore and infrastructure guardrails to resources provisioned in your own AWS or GCP account.
Encore provisions the infrastructure your application needs in your own AWS or GCP account.
// orders/orders.ts import { Topic, Subscription } from "encore.dev/pubsub"; import { sendReceipt } from "./email"; export interface OrderPlaced { orderId: string; email: string; totalCents: number; } // SNS on AWS, Pub/Sub on GCP. export const orderPlaced = new Topic<OrderPlaced>("order-placed", { deliveryGuarantee: "at-least-once", }); // An SQS queue with a dead-letter queue on AWS. const _ = new Subscription(orderPlaced, "send-receipt", { handler: async (event) => { await sendReceipt(event.orderId, event.email); }, retryPolicy: { maxRetries: 5, minBackoff: "10s" }, });

The topic, subscription and retry policy are declared in the service code, and encore run runs the topic and subscriber locally. Teams moving over can bring existing resources along incrementally, as described in the guides for coming from Pulumi and AWS CDK.

For coding agents, a subscription is typed application code, and the platform generates the IAM and networking it implies. Cloud access sits with Encore's IAM role, not the agent.

The infrastructure provisioning docs describe what is created in each environment.

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

AWS CDK: TypeScript that compiles to CloudFormation

AWS CDK organizes infrastructure into stacks of constructs and deploys them through CloudFormation. High-level L2 constructs set sensible defaults, so the queue with a dead-letter queue is short:

import { Stack, StackProps, Duration } from "aws-cdk-lib"; import * as sqs from "aws-cdk-lib/aws-sqs"; import { Construct } from "constructs"; export class OrdersStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const dlq = new sqs.Queue(this, "OrdersDlq", { retentionPeriod: Duration.days(14), }); new sqs.Queue(this, "Orders", { visibilityTimeout: Duration.seconds(30), deadLetterQueue: { queue: dlq, maxReceiveCount: 5 }, }); } }

cdk deploy synthesizes a CloudFormation template and hands it to CloudFormation, which tracks state and rolls back the whole stack if any resource fails. That is CDK's strongest point: no state files to manage and a well-defined recovery path. The costs are speed, since CloudFormation deploys are slower than direct API calls, and reach, since CDK manages only AWS resources. Each account and region also needs a one-time cdk bootstrap.

Pulumi: one engine for many clouds

Pulumi programs declare resources directly, and the engine diffs them against stored state and calls provider APIs:

import * as aws from "@pulumi/aws"; const dlq = new aws.sqs.Queue("orders-dlq", { messageRetentionSeconds: 14 * 24 * 60 * 60, }); export const orders = new aws.sqs.Queue("orders", { visibilityTimeoutSeconds: 30, redrivePolicy: dlq.arn.apply((arn) => JSON.stringify({ deadLetterTargetArn: arn, maxReceiveCount: 5 }), ), });

The apply call shows Pulumi's model of outputs that are known only after deploy, and the same program can manage a Cloudflare DNS record, a Datadog monitor and an AWS queue, and pulumi preview shows the diff before pulumi up applies it. State lives in Pulumi Cloud by default or in a self-managed backend such as S3. There is no automatic rollback: a failed update leaves the resources that succeeded in place, recorded in state, and the next pulumi up continues from there.

Good to know

Both tools leave the application wiring to you. The queue URL, the IAM policy that lets one service consume it, and the code that reads its messages are separate pieces that have to stay in sync across the infrastructure repository and the application.

How to choose

Pick Encore when the infrastructure you manage is mostly what your application uses, such as databases, queues, buckets and cron jobs, and you want it declared in the same TypeScript or Go code, provisioned in your own AWS or GCP account and testable locally.

Pick AWS CDK when you are AWS-only and value CloudFormation's managed state and automatic rollbacks. Pick Pulumi when one program needs to manage resources across several clouds and SaaS providers, or when deploy speed and avoiding CloudFormation limits matter more. For a broader view, see our Encore vs Pulumi and Encore vs AWS CDK comparisons.

Frequently asked questions

What is the difference between Pulumi and AWS CDK?

AWS CDK compiles TypeScript (or Python, Java, C# and Go) into CloudFormation templates, and CloudFormation performs the deploy, keeps the state and rolls back failed updates. It targets AWS only. Pulumi runs its own engine that calls cloud provider APIs directly, keeps state in Pulumi Cloud or a backend you choose, and supports AWS, GCP, Azure, Kubernetes and many other providers.

Is Pulumi or CDK better for a TypeScript team on AWS?

CDK is a natural default when everything is on AWS and you want CloudFormation's managed state and automatic rollbacks at no extra cost. Pulumi fits better when you also manage resources outside AWS, such as Cloudflare, Datadog or GCP, or want faster deploys without CloudFormation's limits.

Can I avoid writing separate infrastructure code at all?

For the resources your application uses directly, yes. With Encore, you declare them in application code, for example new Topic<OrderPlaced>("order-placed", { deliveryGuarantee: "at-least-once" }) and new Subscription(orderPlaced, "send-receipt", { handler }), and Encore provisions SNS and SQS on AWS, or Pub/Sub on GCP, with IAM and dead-letter queues. Shared resources outside the application can stay in Pulumi or CDK.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders