Oct 30, 20265 min read

AWS CDK vs CloudFormation: Which Should Define Your AWS Infrastructure

How the AWS CDK and plain CloudFormation templates differ on language, abstraction, deploys, drift and day-to-day changes, and what it looks like to skip both for application infrastructure.

AWS CloudFormation and the AWS CDK are usually presented as alternatives, but they are layers. CloudFormation is the engine that creates and updates resources from a template. The CDK is a way to write that template in a programming language, and every cdk deploy ends in a CloudFormation stack update.

The real comparison is between writing templates by hand and generating them from code, and how each choice feels on the hundredth change to a backend. This guide covers both, and shows how Encore handles the application part of the infrastructure without either one in your repository.

CDK vs CloudFormation at a glance

EncoreAWS CDKAWS CloudFormation
How you define resourcesIn TypeScript or Go application codeConstructs in TS, Python, Java, C#, GoJSON or YAML templates
Deploy engineEncore platform into your AWS accountCloudFormationCloudFormation
IAMAn IAM role for each servicegrant* helpers on constructsWritten by hand
SetupConnect an AWS accountcdk bootstrap per account and regionNone
Local developmentencore run with local Postgres, Pub/Sub and bucketsNot includedNot included
ScopeApplication infrastructureAnything CloudFormation supportsAnything CloudFormation supports

Each option is covered below, starting with Encore.

Defining infrastructure from application code with Encore

Encore combines an open source infrastructure SDK for TypeScript and Go with a platform that provisions and deploys into your own AWS or GCP account. You declare what the application needs where you use it, and in an AWS environment Encore creates the matching resources: S3 buckets, SNS topics with SQS queues and dead-letter queues, RDS databases, Secrets Manager entries, Fargate or EKS compute, and an IAM role for each service.

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.

An invoices service that stores a file and announces it:

// invoices/invoices.ts import { api } from "encore.dev/api"; import { Bucket } from "encore.dev/storage/objects"; import { Topic } from "encore.dev/pubsub"; // A private S3 bucket on AWS, a local bucket under encore run. export const invoiceFiles = new Bucket("invoice-files", { versioned: false }); interface InvoiceStored { invoiceId: string; key: string; } // An SNS topic on AWS. Each subscription gets its own SQS queue. export const invoiceStored = new Topic<InvoiceStored>("invoice-stored", { deliveryGuarantee: "at-least-once", }); export const store = api( { expose: true, method: "POST", path: "/invoices/:invoiceId" }, async ({ invoiceId, csv }: { invoiceId: string; csv: string }): Promise<{ key: string }> => { const key = `${invoiceId}.csv`; await invoiceFiles.upload(key, Buffer.from(csv), { contentType: "text/csv" }); await invoiceStored.publish({ invoiceId, key }); return { key }; }, );
Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

The service that uploads to the bucket gets permission to write to it, the topic exists because the code publishes to it, and adding a subscriber in another service adds its queue and permissions on the next deploy. encore run runs the same bucket and topic locally, so the endpoint can be tested with curl before anything reaches AWS.

For a coding agent, the change is one TypeScript file instead of a service, a CDK stack and the IAM statements that connect them. The agent works in the code it already understands, and a mistake shows up locally.

AWS CDK: CloudFormation from code

The AWS CDK lets you define resources as constructs in TypeScript, JavaScript, Python, Java, C# or Go. Higher-level constructs bring defaults and helpers, so the bucket and queue above look like this:

import * as s3 from "aws-cdk-lib/aws-s3"; import * as sns from "aws-cdk-lib/aws-sns"; import * as sqs from "aws-cdk-lib/aws-sqs"; import * as subs from "aws-cdk-lib/aws-sns-subscriptions"; // Inside a Stack constructor. apiTaskRole is the ECS task role defined elsewhere in the stack. const bucket = new s3.Bucket(this, "InvoiceFiles", { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); const topic = new sns.Topic(this, "InvoiceStored"); const dlq = new sqs.Queue(this, "InvoiceStoredDlq"); const queue = new sqs.Queue(this, "InvoiceStoredQueue", { deadLetterQueue: { queue: dlq, maxReceiveCount: 5 }, }); topic.addSubscription(new subs.SqsSubscription(queue)); bucket.grantWrite(apiTaskRole); topic.grantPublish(apiTaskRole);

That is much shorter than the equivalent template, and grantWrite writes an IAM policy limited to that bucket. You still need the task role, the service, the load balancer and the environment variables that tell the code which bucket and topic to use. Each AWS account and region needs cdk bootstrap before the first deploy, and when something fails, the error comes from CloudFormation and points at the synthesized template, not at your construct.

AWS CloudFormation: templates as data

CloudFormation takes a JSON or YAML template and creates, updates or rolls back a stack of resources. Change sets preview what an update will do, drift detection compares the stack to what is actually deployed, and stack policies can protect resources from replacement.

Writing templates by hand is verbose. A bucket policy, a queue policy that allows SNS to send, and IAM statements for every action add up, and reuse comes from nested stacks, modules or macros instead of functions. A template is also plain data that any tool can generate or review, with no build step, and a stack can hold up to 500 resources before you have to split it.

Good to know

Choosing the CDK does not take you away from CloudFormation's limits and behavior. Stack resource limits, update rollbacks and resource replacement rules all still apply, so a CDK team still ends up reading CloudFormation events when a deploy goes wrong.

How to choose

Pick Encore when you want the application's databases, buckets, queues, secrets and cron jobs defined next to the code that uses them, and the APIs and services runnable locally. Cron jobs run only in cloud environments, not with encore run.

Pick the AWS CDK when you need to define arbitrary AWS resources and your team prefers code, types and reusable constructs. Pick plain CloudFormation when templates are generated by other tools, reviewed as data, or need to deploy without a build step.

Frequently asked questions

What is the difference between AWS CDK and CloudFormation?

CloudFormation is the AWS service that creates and updates resources from a JSON or YAML template. The AWS CDK is a library and CLI that lets you define those resources in TypeScript, Python, Java, C#, Go or JavaScript and synthesizes them into CloudFormation templates. Every CDK deploy is a CloudFormation deploy.

Should I use CDK or CloudFormation?

Use the CDK when your team writes code and wants loops, types, reusable constructs and sensible defaults such as grant methods for IAM. Use plain CloudFormation when you want templates that are reviewed as data, generated by other tools, or deployed without a build step and bootstrapping.

Can I define AWS infrastructure for my backend in application code?

Yes. With Encore, you declare buckets, topics, subscriptions, databases and secrets in TypeScript or Go application code. Encore provisions S3, SNS, SQS, RDS and Secrets Manager in your own AWS account with an IAM role for each service, and encore run gives you the same resources locally.

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