AWS SAM
Build and deploy serverless applications with SAM: the template shorthand, sam local for testing, guided deploys, and SAM versus CDK.
SAM is a shorthand for CloudFormation aimed at serverless applications, plus a CLI that builds, tests and deploys them. A AWS::Serverless::Function resource expands into a Lambda function, an IAM role, a log group and any triggers you declared. Perhaps forty lines of CloudFormation from ten lines of SAM.
That is the whole proposition: it is CloudFormation, so anything CloudFormation can express you can drop into the same template, and the stack behaves like any other.
A template
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Orders API
Globals:
Function:
Runtime: python3.12
Timeout: 25
MemorySize: 512
Architectures: [arm64]
Tracing: Active
Environment:
Variables:
TABLE_NAME: !Ref OrdersTable
LoggingConfig:
LogFormat: JSON
Resources:
OrdersApi:
Type: AWS::Serverless::HttpApi
Properties:
StageName: prod
CorsConfiguration:
AllowOrigins: ['https://app.example.com']
AllowMethods: [GET, POST, OPTIONS]
AllowHeaders: [content-type, authorization]
GetOrderFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/get_order/
Handler: app.handler
Policies:
- DynamoDBReadPolicy:
TableName: !Ref OrdersTable
Events:
Api:
Type: HttpApi
Properties:
ApiId: !Ref OrdersApi
Path: /orders/{id}
Method: GET
OrdersTable:
Type: AWS::Serverless::SimpleTable
Properties:
PrimaryKey: { Name: orderId, Type: String }
BillingMode: PAY_PER_REQUEST
Outputs:
ApiUrl:
Value: !Sub 'https://${OrdersApi}.execute-api.${AWS::Region}.amazonaws.com/prod'
The Transform line is what makes it SAM. Without it, CloudFormation does not recognise AWS::Serverless::* and the deploy fails with an unknown resource type.
Three parts of that template are doing real work.
Globals applies defaults to every function, which stops the same six properties being repeated per function. It is the main reason a SAM template with ten functions stays readable.
Policies with a policy template: DynamoDBReadPolicy here. Generates a least-privilege role scoped to that table. It is markedly better than the common habit of attaching a broad managed policy, and there are around sixty templates covering the usual combinations.
Events creates the trigger and the permission. Declaring an HttpApi event wires the route and adds the resource-based policy that API Gateway needs to invoke the function. The permission people forget when building integrations by hand.
Architectures: [arm64] is worth making a default: Graviton Lambda is around 20% cheaper and usually slightly faster.
Build and deploy
sam build --use-container
--use-container builds inside a Lambda-like Docker image, which matters for any dependency with native extensions. Building psycopg2 or numpy on macOS and deploying to Lambda produces an import error at runtime. An architecture and libc mismatch of the same family as exec format error. If your dependencies are pure Python or JavaScript you can skip it; if they are not, use it always and in CI.
sam deploy --guided
The guided deploy asks for a stack name, region and parameters, then writes samconfig.toml:
version = 0.1
[default.deploy.parameters]
stack_name = "orders-api"
region = "eu-west-1"
capabilities = "CAPABILITY_IAM"
resolve_s3 = true
[prod.deploy.parameters]
stack_name = "orders-api-prod"
region = "eu-west-1"
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Environment=prod"
After that, sam deploy alone is enough, and sam deploy --config-env prod targets the other environment. Commit samconfig.toml. It is the difference between a reproducible deploy and one that depends on what someone typed.
CAPABILITY_IAM is required because SAM creates roles. A template with named roles needs CAPABILITY_NAMED_IAM instead, which is a common first deploy failure.
Testing locally
sam local invoke GetOrderFunction --event events/get_order.json
sam local start-api --port 3000
curl http://localhost:3000/orders/A-4821
sam local generate-event apigateway http-api-proxy > events/get_order.json
That last command is more useful than it looks: it produces a correctly shaped event for the trigger you name, so you are testing against the real payload structure rather than one you guessed. It covers S3, SQS, SNS, DynamoDB streams and dozens more.
What local testing genuinely gives you is fast iteration on handler logic and confidence in the event shape. What it does not give you is IAM, sam local uses your credentials, so a function that works locally can fail deployed because its role lacks a permission. Local success is not a permissions test.
For iterating against real AWS rather than a local emulation:
sam sync --watch --stack-name orders-api
sam sync pushes code changes directly to the function, skipping CloudFormation for code-only edits. A few seconds instead of a couple of minutes. It is explicitly for development: it can leave the stack differing from the template, so deploy properly before anything else depends on it.
Safe deployments
SAM exposes CodeDeploy's traffic shifting as a few lines:
GetOrderFunction:
Type: AWS::Serverless::Function
Properties:
AutoPublishAlias: live
DeploymentPreference:
Type: Canary10Percent5Minutes
Alarms:
- !Ref ErrorRateAlarm
Hooks:
PreTraffic: !Ref SmokeTestFunction
That publishes a version, shifts 10% of traffic for five minutes, watches the alarm, and rolls back automatically if it fires. Getting equivalent behaviour by hand means versions, aliases, a CodeDeploy application and a deployment group. This is the clearest case where SAM's shorthand earns its keep.
SAM, CDK, Terraform or Serverless Framework
| Best at | Weakness | |
|---|---|---|
| SAM | Pure serverless on AWS | Awkward beyond serverless; YAML |
| CDK | Complex AWS infrastructure in code | Steeper learning curve, generated output is opaque |
| Terraform | Multi-cloud, whole-estate | Verbose for serverless; state to manage |
| Serverless Framework | Multi-cloud serverless, plugins | Third-party; licensing changed for larger users |
Choose SAM when the application is serverless and AWS-only and you want the shortest path from template to deployed stack, with local testing included. Choose CDK when you want real language constructs and are building more than functions, and note the two interoperate, since sam local can test a CDK-synthesised template. Choose Terraform when serverless is a small part of a larger estate you already manage there.
Common errors
Error: Unable to upload artifact ... referenced by CodeUri parameter
sam build was not run, or CodeUri points somewhere that does not exist. Deploy from .aws-sam/build, which sam deploy does automatically after a build.
An error occurred (InsufficientCapabilitiesException) when calling the
CreateChangeSet operation: Requires capabilities: [CAPABILITY_IAM]
Add --capabilities CAPABILITY_IAM, or CAPABILITY_NAMED_IAM if roles are named.
Transform AWS::Serverless-2016-10-31 failed with: Invalid Serverless
Application Specification document
A property is misspelled or in the wrong place. The message lists the offending path; sam validate --lint catches most of these before deploying.
Runtime.ImportModuleError: Unable to import module 'app'
Handler does not match the file and function. It is <file>.<function> relative to CodeUri, with no .py extension. The other cause is a native dependency built for the wrong platform, which sam build --use-container fixes.
Quick reference
| Task | Command |
|---|---|
| Scaffold | sam init |
| Validate | sam validate --lint |
| Build | sam build --use-container |
| First deploy | sam deploy --guided |
| Later deploys | sam deploy / --config-env prod |
| Invoke locally | sam local invoke <Fn> --event e.json |
| Local API | sam local start-api |
| Generate a real event | sam local generate-event <service> <event> |
| Fast dev loop | sam sync --watch --stack-name <s> |
| Tail logs | sam logs -n <Fn> --stack-name <s> --tail |
| Delete | sam delete --stack-name <s> |
Key takeaways
- SAM is CloudFormation, so plain CloudFormation resources mix in freely and the stack is ordinary.
- The
Transformline is mandatory: without itAWS::Serverless::*is unrecognised. Globalskeeps a multi-function template readable.- Policy templates generate least-privilege roles, which beats attaching a broad managed policy.
- An
Eventsentry creates the trigger and the invoke permission together. sam build --use-containerwhenever any dependency has native code.sam localuses your credentials, so it never validates IAM.DeploymentPreferencegives canary deploys with automatic rollback in five lines.- Commit
samconfig.toml.
Check your understanding
Works locally, fails deployed
A function runs correctly under sam local invoke but fails deployed with AccessDeniedException on a DynamoDB call. The template builds and deploys without error. What explains it?
An import error only in Lambda
A Python function using a library with compiled C extensions imports fine on a developer's Mac but fails deployed with Unable to import module. sam build was run without flags. What fixes it?
Frequently Asked Questions
What is the difference between SAM and CloudFormation?
SAM is CloudFormation with a transform that expands serverless shorthand. AWS::Serverless::Function becomes a Lambda function, an execution role, a log group and any triggers you declared, so ten lines produce what would otherwise take forty. Because the result is a normal CloudFormation stack, you can mix ordinary resources into the same template and manage it with the usual tooling. The SAM CLI adds the parts CloudFormation lacks for serverless: building dependencies, local invocation and log tailing.
Should I use SAM or CDK?
SAM when the application is serverless, AWS-only, and you want the shortest path from template to deployment with local testing included. CDK when you are building broader infrastructure and want real programming-language constructs, loops and abstractions rather than YAML. CDK has a steeper learning curve and its synthesised output is harder to read, which matters when debugging. They also interoperate, sam local can test a template CDK synthesised, so the choice is less final than it appears.
When do I need sam build --use-container?
Whenever a dependency includes compiled code. A plain sam build installs packages with your local toolchain, so anything with C extensions is built for your operating system and architecture and fails to import in Lambda. Building inside a Lambda-like container produces artefacts for the right architecture and C library. Pure Python or JavaScript dependencies do not need it, but using it consistently, and in CI, makes builds reproducible rather than dependent on whose machine ran them.
Does sam local test my IAM permissions?
No, and this is the most important limitation to know. sam local runs the handler using your own credentials, which are typically much broader than the function's execution role, so a missing permission passes locally and fails on the first deployed invocation. Local testing validates handler logic and that you are handling the real event shape; authorisation has to be tested against a deployed stack. Use policy templates in the template to keep the role least-privilege rather than guessing.
What does sam sync do and is it safe?
sam sync --watch pushes code changes straight to the deployed functions, bypassing CloudFormation for code-only edits. Seconds instead of a couple of minutes. It is explicitly a development tool: because it skips the stack, the deployed state can drift from the template, so anything shared or production-facing should go through sam deploy. Used on your own development stack it removes most of the friction that makes people avoid deploying to real AWS while iterating.
How do I do canary deployments with SAM?
Add AutoPublishAlias and a DeploymentPreference block to the function. SAM publishes a new version on each deploy, points the alias at it gradually according to the strategy you name, Canary10Percent5Minutes, Linear10PercentEvery1Minute and similar, watches any CloudWatch alarms you list, and rolls back automatically if one fires. Hooks can run pre-traffic and post-traffic validation functions. Building the equivalent by hand means versions, aliases, a CodeDeploy application and a deployment group.