AWS SAM

By Vishvesh Patel · DevOps EngineerUpdated September 16, 2026

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 atWeakness
SAMPure serverless on AWSAwkward beyond serverless; YAML
CDKComplex AWS infrastructure in codeSteeper learning curve, generated output is opaque
TerraformMulti-cloud, whole-estateVerbose for serverless; state to manage
Serverless FrameworkMulti-cloud serverless, pluginsThird-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

TaskCommand
Scaffoldsam init
Validatesam validate --lint
Buildsam build --use-container
First deploysam deploy --guided
Later deployssam deploy / --config-env prod
Invoke locallysam local invoke <Fn> --event e.json
Local APIsam local start-api
Generate a real eventsam local generate-event <service> <event>
Fast dev loopsam sync --watch --stack-name <s>
Tail logssam logs -n <Fn> --stack-name <s> --tail
Deletesam delete --stack-name <s>

Key takeaways

  • SAM is CloudFormation, so plain CloudFormation resources mix in freely and the stack is ordinary.
  • The Transform line is mandatory: without it AWS::Serverless::* is unrecognised.
  • Globals keeps a multi-function template readable.
  • Policy templates generate least-privilege roles, which beats attaching a broad managed policy.
  • An Events entry creates the trigger and the invoke permission together.
  • sam build --use-container whenever any dependency has native code.
  • sam local uses your credentials, so it never validates IAM.
  • DeploymentPreference gives canary deploys with automatic rollback in five lines.
  • Commit samconfig.toml.

Check your understanding

Exercise

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?

Exercise

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.

Continue Learning

Explore Related Topics

When it breaks

Related Resources