04/20/26

AWS CDK Alternatives in 2026

What to use when CloudFormation limits and construct abstractions get in the way

9 Min Read

AWS CDK was a genuine step forward. Writing infrastructure in TypeScript, Python, or Go beat writing CloudFormation YAML by a wide margin, and the L2/L3 construct library made common patterns ergonomic. For AWS-only teams, CDK is still one of the better ways to manage infrastructure.

The friction shows up at the seams. CDK compiles to CloudFormation, which means you inherit CloudFormation's rollback behavior, its drift detection quirks, and its 500-resource-per-stack limit. Deploys are slow because CloudFormation is slow. The L2 constructs sometimes abstract the wrong things, leaving you reaching through escape hatches (node.defaultChild, addPropertyOverride) when the opinions don't fit. And because CDK is AWS-only, a multi-cloud strategy means running CDK plus something else.

This guide covers the realistic alternatives, from cross-cloud IaC tools to approaches that eliminate the separate-infrastructure-program model entirely.

AWS CDK Alternatives: An Overview

FeatureEncorePulumiTerraform / OpenTofuSSTServerless FrameworkAWS SAM
ApproachInfrastructure from codeIaC (imperative)IaC (HCL)IaC (CDK-based, serverless-first)IaC (config-driven)IaC (CloudFormation macro)
LanguageTypeScript / GoTypeScript, Python, Go, C#, JavaHCLTypeScriptYAML + pluginsYAML
Cloud supportAWS, GCP (provisions in your account)AWS, GCP, Azure, 150+ providersAWS, GCP, Azure, 3,900+ providersAWS onlyAWS primary, multi-cloud pluginsAWS only
Backing engineDirect API + Terraform providerPulumi engine (no CloudFormation)Terraform stateCDK → CloudFormationCloudFormationCloudFormation
Infrastructure configZero (derived from app code)Separate programSeparate HCL filesSeparate configSeparate YAMLSeparate YAML
State managementNoneManaged (Pulumi Cloud) or self-managedRemote state fileCloudFormation stacksCloudFormation stacksCloudFormation stacks
Deploy speed (simple stack)FastFastFastSlow (CFN)Slow (CFN)Slow (CFN)
Multi-cloudAWS + GCP nativeYesYesNoLimitedNo
LicenseOpen Source (MPL-2.0)Open Source (Apache 2.0)MPL-2.0 (OpenTofu) / BSL (Terraform)Open Source (MIT)Open Source (MIT)Open Source (Apache 2.0)
Best forTeams who want zero infra managementMulti-cloud, CDK-like DXTeams with existing Terraform estateServerless on AWSLambda-centric appsAWS-native serverless

Encore

Encore takes a fundamentally different approach from every other tool on this list. Instead of writing a separate infrastructure program that provisions resources and then a second application that uses them, you declare what your application needs directly in your TypeScript or Go code. Databases, Pub/Sub topics, cron jobs, object storage, and caching are declared as objects in the same files where you write your business logic. Encore's open-source tooling parses the code to understand the infrastructure requirements, and Encore Cloud provisions the corresponding AWS (or GCP) resources in your own account.

There's no CloudFormation stack, no construct tree, no separate program to build. The application code is the source of truth. Adding a database to a service is one line of code in the same file as the queries that use it.

CDK lets you write infrastructure in TypeScript, but the TypeScript is a CloudFormation generator, it describes resources in a parallel program. Encore's infrastructure declarations sit next to the code that uses them.

Encore provisions real AWS resources in your account (RDS, SQS, SNS, S3, EventBridge, CloudWatch). Production defaults are applied automatically (private VPC placement, encryption at rest, least-privilege IAM, CloudWatch logging), so you only configure what you want to override.

import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { Topic } from "encore.dev/pubsub";
import { CronJob } from "encore.dev/cron";

// Provisions managed Postgres (RDS in AWS production; Docker locally).
const db = new SQLDatabase("orders", { migrations: "./migrations" });

// Provisions SNS topic + SQS subscriptions automatically.
interface OrderPlaced { orderId: string; userId: string; }
export const orderPlaced = new Topic<OrderPlaced>("order-placed", {
  deliveryGuarantee: "at-least-once",
});

// Provisions EventBridge scheduled rule.
new CronJob("daily-reports", {
  title: "Send daily reports",
  every: "24h",
  endpoint: generateReports,
});

export const placeOrder = api(
  { method: "POST", path: "/orders", expose: true },
  async (req: PlaceOrder): Promise<Order> => {
    const order = await db.queryRow`INSERT INTO orders ...`;
    await orderPlaced.publish({ orderId: order.id, userId: req.userId });
    return order;
  },
);

Deploys are faster than CDK because Encore isn't routing through CloudFormation. Changes go directly to the AWS APIs (with a Terraform provider as an escape hatch for non-Encore resources).

Good fit for: teams on AWS or GCP who want to skip the construct tree, stack management, and CloudFormation slowness entirely.

Less good fit for: teams committed to Azure (works via Terraform provider), or teams with heavy existing CDK investment they don't want to migrate away from.

Groupon and other production teams use Encore to manage their AWS backends. It's open source (11k+ GitHub stars).

Deploy with Encore

Want to jump straight to a running app? Clone this starter and deploy it to your own cloud.

Deploy

Pulumi

Pulumi is the closest direct competitor to CDK. You write infrastructure in TypeScript, Python, Go, C#, or Java, and Pulumi provisions resources directly through cloud SDKs, no CloudFormation in the middle.

import * as aws from "@pulumi/aws";

const bucket = new aws.s3.Bucket("my-bucket", {
  versioning: { enabled: true },
});

export const bucketName = bucket.id;

Advantages over CDK:

  • Multi-cloud. Same programming model works for AWS, GCP, Azure, Kubernetes.
  • No CloudFormation. Deploys are faster and failures are more legible.
  • Richer language support. C# and Java alongside TypeScript/Python.

Tradeoffs:

  • State is managed by Pulumi Cloud (or you self-host). You're trading CloudFormation state for Pulumi state.
  • Pulumi-specific SDK per cloud, the APIs don't line up with cloud-native tooling the way CDK's do with AWS.
  • Infrastructure is still a separate program from your application.

Good fit for: teams who like CDK's programming-language approach but need multi-cloud or want to shed CloudFormation.

Terraform / OpenTofu

Terraform (HashiCorp, BSL-licensed since 2023) and OpenTofu (MPL-2.0 fork) remain the most widely-used IaC tools. HCL is its own thing, state files are a known quantity, and the ecosystem of providers is enormous.

resource "aws_s3_bucket" "main" {
  bucket = "my-bucket"
}

resource "aws_s3_bucket_versioning" "main" {
  bucket = aws_s3_bucket.main.id
  versioning_configuration {
    status = "Enabled"
  }
}

Advantages over CDK:

  • Provider coverage is vast (3,900+ providers for OpenTofu).
  • HCL is declarative, which some teams prefer over imperative code for infra.
  • Large existing workforce knows it.

Tradeoffs:

  • HCL doesn't compose like a real language. Modules help; they're not the same.
  • State file management is its own discipline (remote state, locking, drift).
  • AI coding tools generate HCL less fluently than they generate TypeScript.

Good fit for: teams with existing Terraform estates or who prefer declarative configuration. See our Terraform Alternatives page for more.

SST

SST is built on top of CDK with a serverless-first opinion. It wraps CDK constructs into higher-level abstractions ("Api", "Bucket", "Table") aimed at Lambda + API Gateway + DynamoDB shaped applications.

import { StackContext, Api } from "sst/constructs";

export function API({ stack }: StackContext) {
  const api = new Api(stack, "api", {
    routes: { "GET /": "packages/functions/src/lambda.handler" },
  });
  stack.addOutputs({ ApiEndpoint: api.url });
}

Advantages over raw CDK:

  • Faster dev loop with live Lambda (sst dev runs your functions locally while using real AWS resources).
  • Higher-level constructs for common serverless patterns.
  • Integrated deploy story for the app + the infrastructure.

Tradeoffs:

  • AWS-only, like CDK.
  • Still backed by CloudFormation, so the underlying slowness and limits apply.
  • Strong opinions on serverless, less good for containerized or traditional-server apps.

Good fit for: teams building Lambda-heavy AWS apps who want more ergonomic abstractions than raw CDK. See our SST Alternatives for a broader take.

Serverless Framework

Serverless Framework predates both CDK and SST. It's YAML-driven with a plugin ecosystem, focused on Lambda deployments, and works with multiple clouds via plugins.

service: my-service
provider:
  name: aws
  runtime: nodejs20.x

functions:
  hello:
    handler: handler.hello
    events:
      - httpApi: "GET /"

Advantages over CDK:

  • YAML is accessible without learning a programming-language SDK.
  • Plugin ecosystem for non-Lambda needs.
  • Fast path from zero to a deployed function.

Tradeoffs:

  • YAML gets unwieldy at scale.
  • Plugin quality varies.
  • Serverless Inc. has shifted focus to a paid platform, which has created some community uncertainty.

Good fit for: small Lambda deployments where YAML config is enough.

AWS SAM

AWS SAM (Serverless Application Model) is AWS's official serverless IaC tool. It's a CloudFormation macro that expands shorthand SAM resources into full CloudFormation.

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31

Resources:
  HelloFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.handler
      Runtime: nodejs20.x
      Events:
        Api:
          Type: Api
          Properties:
            Path: /
            Method: get

Advantages over CDK:

  • AWS-native, always up to date with AWS services the day they launch.
  • Lightest conceptual overhead for pure Lambda apps.

Tradeoffs:

  • AWS-only, CloudFormation-backed, YAML.
  • Less ergonomic than CDK or SST once you need anything beyond a simple Lambda.

Good fit for: teams deep in AWS who want the most official, AWS-supported path.

How to Choose

Stay on CDK if:

  • You're AWS-only and your team is productive with the construct library.
  • The CloudFormation slowness and limits aren't painful enough to justify migration cost.
  • You have meaningful CDK investment already.

Move to Encore if:

  • You want to eliminate the separate-infrastructure-program pattern entirely.
  • You're on AWS or GCP and want managed defaults without assembling them yourself.
  • You want infrastructure that AI coding agents can read and modify along with the application code.

Move to Pulumi if:

  • You need multi-cloud.
  • You like CDK's language approach but want to escape CloudFormation.

Move to Terraform / OpenTofu if:

  • You have existing Terraform modules or team expertise.
  • You prefer declarative HCL over imperative code for infra.
  • You need the vast provider ecosystem.

Move to SST if:

  • You're committed to AWS serverless and want better ergonomics than raw CDK.

Stay on Serverless Framework or SAM only if you're already there and not feeling pain.

Code Comparison: A Postgres DB + Queue + API

The same system across three tools to make the difference concrete.

AWS CDK

const vpc = new ec2.Vpc(this, "Vpc");
const db = new rds.DatabaseInstance(this, "Db", {
  engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_16 }),
  vpc,
  credentials: rds.Credentials.fromGeneratedSecret("admin"),
});
const queue = new sqs.Queue(this, "Queue");
const api = new apigw.RestApi(this, "Api");
// ...plus Lambda definitions, VPC config, IAM roles, and wiring

Plus a separate application program that reads env vars at runtime to find the DB and queue.

Pulumi

const db = new aws.rds.Instance("db", {
  engine: "postgres",
  instanceClass: "db.t3.micro",
  allocatedStorage: 20,
});
const queue = new aws.sqs.Queue("queue");
// ...plus Lambda and API Gateway, same ballpark as CDK

Same shape, different engine under the hood.

Encore

const db = new SQLDatabase("orders", { migrations: "./migrations" });

const queue = new Topic<OrderEvent>("order-events", {
  deliveryGuarantee: "at-least-once",
});

export const placeOrder = api(
  { method: "POST", path: "/orders", expose: true },
  async (req: PlaceOrder): Promise<Order> => {
    const order = await db.queryRow`INSERT INTO orders ...`;
    await queue.publish({ orderId: order.id });
    return order;
  },
);

That's the whole thing. The infrastructure and the application are the same program. There's no IAM wiring because Encore applies least-privilege automatically based on what each service accesses.

Getting Started

# Encore
brew install encoredev/tap/encore
encore app create my-app --example=ts/empty
cd my-app && encore run

# Pulumi
curl -fsSL https://get.pulumi.com | sh
pulumi new aws-typescript

# Terraform / OpenTofu
brew install opentofu
tofu init
Deploy with Encore

Want to jump straight to a running app? Clone this starter and deploy it to your own cloud.

Deploy

Ready to build your next backend?

Encore is the Open Source framework for building robust type-safe distributed systems with declarative infrastructure.