Skip to content

AWS Infrastructure as Code options

AWS offers several ways to describe infrastructure as code. They are not really competitors: every one of them ultimately produces or deploys a CloudFormation stack, so the choice is about the authoring experience and the governance model rather than about capability.

CloudFormation is the general-purpose service. It can manage any AWS resource type, in declarative JSON or YAML, with no language runtime involved.

SAM is a specialisation for serverless applications, implemented as a CloudFormation transform. A SAM template is a CloudFormation template; the transform expands its high-level types into ordinary resources before the stack is created.

The same Lambda function, both ways:

# CloudFormation
Resources:
MyFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Runtime: nodejs24.x
Role: !GetAtt MyFunctionRole.Arn
Code:
ZipFile: |
exports.handler = async () => ({ statusCode: 200, body: 'Hello' });
# ...plus MyFunctionRole, plus a log group, plus API Gateway resources
# SAM
Transform: AWS::Serverless-2016-10-31
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs24.x
InlineCode: |
exports.handler = async () => ({ statusCode: 200, body: 'Hello' });
Events:
Api:
Type: HttpApi
Properties:
Path: /hello
Method: get

The differences that matter:

  • Abstraction. SAM generates the execution role, the log group and the API Gateway wiring; CloudFormation requires each to be declared.
  • Coverage. SAM’s high-level types cover functions, APIs, simple tables, state machines and layers. Everything else is written as ordinary CloudFormation in the same file.
  • Local testing. The SAM CLI runs functions and APIs locally against a container that matches the Lambda execution environment. CloudFormation has no equivalent.
  • Deployment ergonomics. sam build and sam deploy wrap packaging, change-set creation and execution.

Use SAM for serverless-dominated workloads and CloudFormation for everything else — and note that “mixing” them is not a compromise, it is the normal case.

The CDK defines infrastructure in TypeScript, Python, Java, C# or Go and synthesises a CloudFormation template from it.

import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
export class MyCdkStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
new s3.Bucket(this, 'MyBucket', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
}
}

What it buys: loops, conditionals and abstractions in a real language; high-level constructs that apply sensible defaults; reusable components published as packages; type checking and IDE completion; and unit tests over the synthesised template.

What it costs: a build step, a language runtime in the pipeline, and a layer of indirection between what is written and what is deployed. A construct that silently creates nine resources is convenient until something in it needs to be different. Local Lambda testing under the CDK is done through the SAM CLI, not by the CDK itself.

Service Catalog is a governance layer rather than an authoring tool. A platform team publishes approved CloudFormation templates as products, groups them into portfolios, and shares those portfolios with accounts or with the whole organisation. A team then provisions a product without holding permissions on the underlying services, because the provisioning is done by a launch role held by Service Catalog.

It answers a different question from the other three: not “how do I describe this infrastructure” but “how do I let fifty teams provision infrastructure without giving fifty teams administrator access”. See cross-account infrastructure deployment.

CloudFormationSAMCDKService Catalog
Primary useGeneral infrastructure as codeServerless applicationsProgrammatic IaCGoverned self-service provisioning
Authored inYAML or JSONYAMLTypeScript, Python, Java, C#, GoCloudFormation templates
Learning curveModerateLowModerate to highLow for consumers
FlexibilityHighMediumVery highMedium
Local testingNoYes, via the SAM CLIVia the SAM CLINo
OutputA stackA stackA stackA provisioned product, backed by a stack

CloudFormation when the workload is not serverless, when declarative templates are preferred, or when a template has to be readable by people who do not run the build.

SAM when the workload is mostly Lambda, API Gateway, DynamoDB and Step Functions, and local invocation matters.

CDK when the infrastructure is complex enough that loops, conditionals and shared abstractions earn their keep, and when the team is comfortable owning a build step in the deployment path.

Service Catalog when the problem is who may provision what, rather than how it is described.

They coexist. A common arrangement is CDK or CloudFormation for platform infrastructure, SAM for individual serverless services, and Service Catalog publishing the approved patterns to application teams.

Older material lists AWS Proton as a fifth option for templated microservice and container deployments. It should not be adopted: AWS closed it to new sign-ups on 7 October 2025, and support ends on 7 October 2026, after which the console and Proton resources become inaccessible. Infrastructure already deployed by Proton remains intact.

The governance role Proton played — a platform team publishing standard stacks that application teams instantiate — is covered by Service Catalog together with a CDK or CloudFormation library of shared constructs.

  • All of them produce CloudFormation stacks, so stack limits, change sets, drift detection and rollback behaviour apply to all of them equally.
  • All of them can be used in the same organisation, and frequently in the same account.
  • The deciding factors are team skills, the shape of the workload, how many teams have to consume the result, and how much governance the estate needs.