Home β€Ί AWS Resources β€Ί CDK vs CloudFormation

CDK vs CloudFormation

Infrastructure as code on AWS: when to use CDK vs CloudFormation, the migration path, and why CDK wins for most teams.

The Short Answer

Use CDK unless you have a specific reason not to. CDK generates CloudFormation behind the scenes, so you get the same deployment engine with a much better developer experience.

What's the Difference?

CloudFormation: Declarative JSON/YAML templates. You describe the desired state of every resource and its properties. AWS figures out the order and creates/updates/deletes resources to match.

CDK (Cloud Development Kit): Write infrastructure in a real programming language (TypeScript, Python, C#, Java, Go). CDK synthesizes your code into CloudFormation templates, then deploys them.

They deploy through the same engine. CDK is a better way to author what CloudFormation deploys.

Why CDK Wins

Abstractions reduce boilerplate

Creating a Lambda function with an API Gateway endpoint in CloudFormation: ~150 lines of YAML (function, role, policy, permission, API, method, deployment, stage).

In CDK:

const api = new HttpApi(this, 'Api');
api.addRoutes({
  path: '/orders',
  methods: [HttpMethod.GET],
  integration: new HttpLambdaIntegration('GetOrders', orderFunction),
});

CDK's constructs handle the roles, permissions, and wiring automatically.

Real programming constructs

  • Loops: Create 10 SQS queues with DLQs in a for loop
  • Conditionals: Provision different resources per environment
  • Functions: Reusable infrastructure patterns as libraries
  • Type checking: Catch errors before deployment
  • IDE support: Autocomplete, go-to-definition, refactoring

Shareable construct libraries

Package patterns as libraries. Your "standard Lambda" construct can enforce tagging, logging, tracing, and security defaults. Every team gets it by importing the library.

Less error-prone

CloudFormation's !Ref, !GetAtt, !Sub, and intrinsic functions are hard to debug. CDK's typed references catch errors at compile time.

When CloudFormation Still Makes Sense

  • Simple templates maintained by non-developers (ops teams who know YAML but not TypeScript)
  • AWS Service Catalog products (CloudFormation-native)
  • StackSets for multi-account/multi-region deployment (CDK supports this, but it's more natural in CloudFormation)
  • When you don't want a build step (CloudFormation deploys directly; CDK requires synthesis)
  • Regulatory environments where auditors want to review the exact template being deployed (you can still use CDK and audit the synthesized output)

What About Terraform?

Terraform is the other major option. How it compares:

CDK Terraform CloudFormation
Language TypeScript, Python, C#, etc. HCL (custom language) YAML/JSON
State Managed by CloudFormation Self-managed (S3 + DynamoDB lock) Managed by CloudFormation
Multi-cloud AWS only Multi-cloud AWS only
AWS support Day-1 for new services Days-to-weeks lag Day-1
Drift detection CloudFormation handles it terraform plan shows drift Native
Community modules Construct Hub Terraform Registry Limited

Use Terraform if: You're multi-cloud, your team already knows it, or your organization standardized on it.

Use CDK if: You're AWS-only, you want the best developer experience, and you value type safety and IDE support.

Migration Path (CloudFormation β†’ CDK)

You don't have to rewrite everything at once:

Option 1: Import existing stacks

CDK can import existing CloudFormation resources into a CDK-managed stack. You write the CDK code to match existing resources, then import them.

Option 2: New resources in CDK, existing stay in CloudFormation

Use CDK for new infrastructure. Reference existing CloudFormation outputs using Fn.importValue() or SSM parameters. Migrate stacks over time.

Option 3: Wrap existing templates

CDK's CfnInclude lets you import a CloudFormation template into CDK and progressively replace resources with higher-level constructs.

CDK Concepts (Quick Reference)

Constructs (L1, L2, L3)

  • L1 (Cfn)*: 1:1 mapping to CloudFormation resources. Raw, verbose.
  • L2: Opinionated defaults, sensible security. new Bucket() creates an encrypted, private bucket by default.
  • L3 (Patterns): Multi-resource patterns. ApplicationLoadBalancedFargateService creates ALB + ECS service + task definition + security groups.

Stacks

A deployable unit. Maps to one CloudFormation stack. Keep stacks focused. One per microservice or per concern (networking, database, compute).

Apps

The top-level container. One CDK app can synthesize multiple stacks.

Best Practices

  • One stack per service (or per lifecycle). Don't put your VPC, database, and application in one stack. You can't update compute without risking network changes.
  • Use L2 constructs by default. They set good defaults (encryption, security groups, IAM policies).
  • Share constructs as packages. Standardize your organization's patterns in a private npm/NuGet package.
  • Pin CDK versions. The CDK releases weekly. Pin to a specific version and update intentionally.
  • Use cdk diff before deploying. See what will change before it changes.

Further Reading

Looking for hands-on help? View my AWS architecture services β†’

Modernizing your infrastructure code?

Drop me a message β€” I typically respond within one business day.