Skip to content

AWS SAM

The AWS Serverless Application Model is an open-source framework that extends CloudFormation for serverless applications. A SAM template is a CloudFormation template carrying Transform: AWS::Serverless-2016-10-31; the transform expands SAM’s high-level resource types into the underlying Lambda functions, API Gateway resources, IAM roles and log groups before the stack is created. Anything CloudFormation can do, a SAM template can also do, because it is one.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: nodejs24.x
Timeout: 10
MemorySize: 512
Tracing: Active
Resources:
HelloFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/hello/
Handler: index.handler
Events:
HelloApi:
Type: HttpApi
Properties:
Path: /hello
Method: get

Globals sets defaults for every function in the template, which is how a runtime upgrade becomes a one-line change rather than a search and replace.

Runtime identifiers expire. Lambda deprecates runtimes on a published schedule and eventually blocks creating and then updating functions that use a deprecated one, so check the Lambda runtime support table rather than copying a runtime string from an older template.

TypeExpands to
AWS::Serverless::FunctionLambda function, execution role, log group, event source mappings
AWS::Serverless::ApiAPI Gateway REST API, stage and deployment
AWS::Serverless::HttpApiAPI Gateway HTTP API
AWS::Serverless::SimpleTableDynamoDB table with a single primary key
AWS::Serverless::StateMachineStep Functions state machine and its role
AWS::Serverless::LayerVersionLambda layer version
AWS::Serverless::ApplicationA nested application from the Serverless Application Repository or a local template

Mixing plain CloudFormation resources into the same template is normal and expected — use SAM types where they help and AWS:: types everywhere else.

SAM can generate a scoped execution role from a named policy template, which is usually tighter than the managed policy someone would otherwise reach for:

MyFunction:
Type: AWS::Serverless::Function
Properties:
Policies:
- S3ReadPolicy:
BucketName: !Ref MyBucket
- DynamoDBCrudPolicy:
TableName: !Ref MyTable
SharedLayer:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: ./layer
CompatibleRuntimes:
- nodejs24.x

The SAM CLI runs functions and APIs locally in a container that matches the Lambda execution environment:

Terminal window
sam local start-api # local API Gateway in front of the functions
sam local invoke # invoke one function with a test event
sam local start-lambda # local Lambda endpoint for SDK clients
sam local generate-event apigateway aws-proxy > event.json

This is SAM’s main advantage over writing CloudFormation directly, and it is available to CDK users too — the CDK relies on the SAM CLI for local Lambda testing.

Terminal window
sam build # resolve dependencies, produce build artefacts
sam deploy --guided # interactive first deployment, writes samconfig.toml
sam deploy # subsequent deployments from that config
sam logs -n MyFunction --tail
sam traces

sam deploy creates a CloudFormation change set and executes it, so a SAM deployment is a CloudFormation deployment with the same rollback behaviour.

graph LR A[SAM template] -->|sam build| B[Build artefacts] B -->|sam deploy| C[CloudFormation change set] C --> D[AWS] subgraph "Local development" E[sam local start-api] --> F[Local testing] G[sam local invoke] --> F end subgraph "CI/CD" H[Git provider] -->|Trigger| I[Build pipeline] I -->|sam deploy| D end subgraph "Observability" D -->|sam logs| J[CloudWatch Logs] D -->|sam traces| K[X-Ray] end

The feature that makes SAM more than syntactic sugar is its built-in integration with CodeDeploy for gradual traffic shifting between Lambda function versions.

PaymentFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/payment/
Handler: index.handler
AutoPublishAlias: live
DeploymentPreference:
Type: Canary10Percent5Minutes
Alarms:
- !Ref PaymentErrorAlarm
Hooks:
PreTraffic: !Ref PreTrafficCheckFunction
PostTraffic: !Ref PostTrafficCheckFunction

What each part does:

  • AutoPublishAlias publishes a new function version on every deployment and points an alias at it, which is what gives CodeDeploy two versions to shift between.
  • DeploymentPreference.Type chooses the shift pattern — AllAtOnce, Linear10PercentEvery1Minute, Canary10Percent5Minutes and similar.
  • Alarms names CloudWatch alarms that abort and roll back the deployment automatically if they enter ALARM during the shift. This is the part that makes the strategy safe rather than merely gradual.
  • Hooks names pre-traffic and post-traffic Lambda functions: the pre-traffic hook validates the new version before any production traffic reaches it, and the post-traffic hook runs after the shift completes. Either can fail the deployment.

The combination — canary shifting, validation hooks, alarm-triggered rollback — is why SAM plus CodeDeploy is a reasonable default for Lambda delivery, and all of it is declared in the template rather than assembled in a pipeline.

AWS CDK. More programmatic control in TypeScript, Python, Java or C#, better suited to complex infrastructure where a general-purpose language earns its keep. It supports the same CodeDeploy traffic shifting, and it uses the SAM CLI for local testing.

Terraform. Sensible when Terraform already manages the rest of the estate and a single state and workflow matter more than serverless-specific ergonomics.

Direct CLI or console deployment. Fastest for a prototype; unsuitable for anything with users, because there is no reviewable record of what changed.

  • Put configuration in environment variables and secrets in Secrets Manager or Parameter Store; never in the template.
  • Set timeouts and memory deliberately. The default timeout is too short for some work and far too long for a function that should fail fast, and memory is also the CPU dial.
  • Use layers for shared code, and pin their compatible runtimes.
  • Enable X-Ray tracing (Tracing: Active) from the start; retrofitting tracing during an incident is not an option.
  • Handle errors explicitly and configure a dead-letter queue or an on-failure destination, so a poison event is visible rather than silently retried.
  • Integrate with the pipeline through CodePipeline or GitHub Actions rather than deploying from a workstation.