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.
Each option is covered below, starting 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.

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 };
},
);
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.
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.
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.
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.
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.
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.
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.
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.