Every serverless tutorial eventually runs into the same wall: you can write a Lambda function in five minutes, but wiring it to API Gateway, DynamoDB, IAM roles, and a repeatable deployment pipeline is a different job entirely. That gap is exactly what AWS SAM (Serverless Application Model) was built to close. As of August 2026, the AWS SAM CLI sits at version 1.162.1 on GitHub, with a mirrored build at 1.165.0 already shipping through SourceForge, and AWS has spent the last few months adding BuildKit-based container builds and CloudFormation Language Extensions support directly into the tool.
This tutorial walks through installing AWS SAM CLI, scaffolding a real serverless application, testing it locally with Docker before it ever touches AWS, and deploying it with a single guided command. By the end you will have a working API backed by Lambda and DynamoDB, a CI/CD pipeline that redeploys on every push, and a clear picture of where AWS SAM fits next to AWS CDK, the Serverless Framework, and Terraform.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Is AWS SAM and Why It Matters in 2026
AWS SAM is an open-source framework that extends AWS CloudFormation with a shorthand syntax for serverless resources. Instead of hand-writing dozens of lines of CloudFormation to define a Lambda function, its execution role, and an API Gateway trigger, you write a handful of lines under a AWS::Serverless::Function resource type, and SAM expands it into full CloudFormation at build time. According to AWS’s own developer guide, the AWS SAM CLI allows you to build, transform, deploy, debug, package, initialize, and sync your serverless applications locally before deploying to the cloud, which is the core reason teams reach for it over writing raw CloudFormation by hand.
The tool itself costs nothing. It’s distributed as an open-source binary on the aws/aws-sam-cli GitHub repository, which has shipped well over 250 releases and continues to cut new versions on a near-weekly cadence. You only pay for the AWS resources your templates actually create, meaning Lambda invocations, DynamoDB reads, and API Gateway requests, not for the CLI or the CloudFormation transform it relies on.
Two features shipped in May 2026 make this a good moment to pick SAM up if you haven’t already. AWS added BuildKit support for Lambda container image builds, letting sam build --use-buildkit use layer caching and parallel builds instead of the older Docker build path, a change that requires SAM CLI 1.159.0 or newer. AWS also added support for CloudFormation Language Extensions inside SAM templates, so large applications can cut down on repeated boilerplate while keeping SAM’s local-invoke and local-API workflows intact. Neither feature is mandatory for a first project, but both are worth knowing about once your templates grow past a single function.
It’s worth being clear about what SAM is not, too. It isn’t a replacement for Lambda, API Gateway, or DynamoDB themselves, it’s a layer that describes and deploys them. It also isn’t a runtime; your function code still runs inside the same managed Lambda execution environment whether you deployed it through SAM, the console, or raw CloudFormation. What SAM actually buys you is the workflow around that code: a template format that’s shorter to write and easier to review than raw CloudFormation, a CLI that can emulate the deployed environment on your laptop before you spend a single second waiting on a CloudFormation changeset, and a deploy command that turns “did I remember every IAM permission and every trigger wiring” into a solved problem instead of a checklist you re-derive by hand each time.
Prerequisites: Tools, Versions, and Accounts You’ll Need
Before starting, confirm you have the following installed and configured. Version numbers below reflect what’s current as of late August 2026; SAM CLI in particular updates often, so always check sam --version against the latest GitHub release before troubleshooting version-specific bugs.
| Tool | Minimum Version | Purpose |
|---|---|---|
| AWS CLI | v2.15 or newer | Authenticate and manage AWS resources from your terminal |
| AWS SAM CLI | 1.159.0+ (1.162.1 latest as of June 2026) | Build, test, and deploy the serverless application |
| Docker Desktop or Docker Engine | 24.x or newer | Runs Lambda locally in a container that matches the real runtime |
| Python | 3.12 or 3.13 | Runtime for the example Lambda function (Node.js 22, Java 21, and .NET 8 are equally supported) |
| An AWS account with an IAM user | N/A | Deploy target with programmatic access keys or SSO credentials |
You do not need a paid AWS account to follow along. AWS Lambda carries a permanent free tier of 1 million requests and 400,000 GB-seconds of compute per month that never expires and applies to both x86 and ARM/Graviton functions. DynamoDB’s always-free tier adds 25 GB of storage and 200 million requests a month on top of that, which is more than enough to build and redeploy this tutorial’s project dozens of times without a bill.
Steps 1-3: Install and Configure Your SAM Environment
Step 1: Install the AWS CLI
If you don’t already have the AWS CLI, install it first since SAM CLI shells out to it for credential resolution.
# macOS
brew install awscli
# Windows (PowerShell, run as Administrator)
msiexec /i https://awscli.amazonaws.com/AWSCLIV2.msi
# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# verify
aws --version
Step 2: Install the AWS SAM CLI
SAM CLI ships through Homebrew on macOS, an MSI installer on Windows, and a downloadable zip on Linux. AWS’s own install documentation tracks version-specific release notes if you need to pin a particular build for a CI runner.
# macOS
brew tap aws/tap
brew install aws-sam-cli
# Linux (manual)
wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
sudo ./sam-installation/install
# verify — should print 1.159.0 or later
sam --version
Step 3: Configure IAM Credentials
Create an IAM user (or use SSO) with programmatic access, then run aws configure. For a first project, attaching AdministratorAccess temporarily is fine; once you understand which permissions SAM actually needs (CloudFormation, Lambda, IAM role creation, S3 for the deployment bucket, and whatever resources your template defines), scope the policy down. This distinction matters enough that it shows up again in the pitfalls section below.
aws configure
AWS Access Key ID [None]: AKIA................
AWS Secret Access Key [None]: ................
Default region name [None]: us-east-1
Default output format [None]: json
Steps 4-6: Build Your First Serverless Application
Step 4: Scaffold a Project With sam init
Run sam init and pick the “Hello World Example” quick-start template with the Python 3.13 runtime. This generates a working project structure so you can see how AWS itself organizes a SAM app before customizing anything.
sam init
# 1 - AWS Quick Start Templates
# Choose runtime: python3.13
# Project name: sam-url-shortener
# Template: Hello World Example
cd sam-url-shortener
tree -L 2
You’ll get a directory with template.yaml at the root, a hello_world/ folder containing app.py, and a tests/ folder with unit and integration test stubs. That layout is the entire mental model for a SAM project: one YAML template describing infrastructure, and one folder per function containing its code.
Step 5: Understand the SAM Template
Open template.yaml. This is the file that separates SAM from a plain Python or Node project: it’s a CloudFormation template with a Transform: AWS::Serverless-2016-10-31 line at the top, which tells CloudFormation to expand SAM’s shorthand resource types into full CloudFormation before deploying. Replace the generated contents with the following, which defines a Lambda function, an HTTP API trigger, and a DynamoDB table in about 30 lines:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A serverless URL shortener built with SAM
Globals:
Function:
Timeout: 10
MemorySize: 256
Runtime: python3.13
Architectures:
- arm64
Resources:
ShortenFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: shorten_url/
Handler: app.lambda_handler
Environment:
Variables:
TABLE_NAME: !Ref UrlTable
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UrlTable
Events:
ShortenApi:
Type: HttpApi
Properties:
Path: /shorten
Method: post
UrlTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: shortCode
AttributeType: S
KeySchema:
- AttributeName: shortCode
KeyType: HASH
Outputs:
ApiUrl:
Description: HTTP API endpoint
Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/shorten"
Notice the DynamoDBCrudPolicy line. This is a SAM policy template, one of dozens of pre-built least-privilege IAM policy snippets that expand into scoped permissions automatically. Writing that same IAM policy by hand in raw CloudFormation takes fifteen to twenty lines; SAM collapses it to two.
Step 6: Write the Lambda Function Handler
Create a folder named shorten_url/ with an app.py inside it. This function generates a short code, writes it to DynamoDB, and returns the mapping.
import json
import os
import boto3
import hashlib
import time
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["TABLE_NAME"])
def lambda_handler(event, context):
body = json.loads(event.get("body") or "{}")
long_url = body.get("url")
if not long_url:
return {"statusCode": 400, "body": json.dumps({"error": "Missing 'url' in request body"})}
short_code = hashlib.sha256(f"{long_url}{time.time()}".encode()).hexdigest()[:7]
table.put_item(Item={
"shortCode": short_code,
"longUrl": long_url,
"createdAt": int(time.time())
})
return {
"statusCode": 201,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"shortCode": short_code, "longUrl": long_url})
}
Add a shorten_url/requirements.txt file containing boto3, though in practice boto3 already ships inside the Lambda Python runtime, so this line mostly matters for local testing consistency.
Steps 7-9: Test Locally and Deploy to AWS
Step 7: Build the Application
Run sam build. Per AWS’s own command reference, this step gathers the build artifacts of your application’s dependencies and places them in the proper format and location for next steps, such as locally testing, packaging, and deploying. It resolves your requirements.txt, installs dependencies into a build-friendly directory, and produces a transformed template SAM can deploy or invoke locally.
sam build
# expected output tail:
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
[*] Deploy: sam deploy --guided
Step 8: Test Locally With Docker
With Docker running, invoke the function directly using a sample event, then start a local API server to test the full HTTP path before anything touches AWS.
# invoke once with a test payload
echo '{"body": "{\"url\": \"https://tech-insider.org\"}"}' | sam local invoke ShortenFunction -e /dev/stdin
# or run a full local API on port 3000
sam local start-api
# in a second terminal:
curl -X POST http://127.0.0.1:3000/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://tech-insider.org"}'
# expected response:
# {"shortCode": "a91f3c2", "longUrl": "https://tech-insider.org"}
sam local start-api spins up a Docker container running the exact Lambda runtime you specified, mounts your built code inside it, and proxies HTTP requests to it the same way API Gateway would in production. This is the step that catches the majority of “works locally, breaks in the cloud” bugs before a deploy ever happens.
Step 9: Deploy With sam deploy –guided
For a first deployment, AWS’s own guidance is direct: when you deploy a serverless application for the first time, use the –guided option. This walks you through naming the CloudFormation stack, picking a region, and confirming IAM role creation, then saves those answers to a samconfig.toml file so every future deploy is a plain sam deploy.
sam deploy --guided
Stack Name [sam-app]: sam-url-shortener
AWS Region [us-east-1]:
Confirm changes before deploy [y/N]: y
Allow SAM CLI IAM role creation [Y/n]: y
Disable rollback [y/N]: n
Save arguments to configuration file [Y/n]: y
# ...
Successfully created/updated stack - sam-url-shortener in us-east-1
Outputs:
ApiUrl: https://abc123xyz.execute-api.us-east-1.amazonaws.com/shorten
Under the hood, the sam deploy command deploys an application to the AWS Cloud using AWS CloudFormation, packaging your code into a deployment bucket, uploading it, and submitting a CloudFormation changeset. That’s why every SAM app shows up as a normal stack in the CloudFormation console, and why rolling back a bad deploy is just a CloudFormation rollback under the hood.
Steps 10-12: Add Real Infrastructure, CI/CD, and Cleanup
Step 10: Confirm the Live Endpoint
Grab the ApiUrl output from the deploy and hit it directly. The DynamoDB table, IAM role, and API Gateway route were all created from the same template you tested locally, so the response should be identical to Step 8.
curl -X POST https://abc123xyz.execute-api.us-east-1.amazonaws.com/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://tech-insider.org"}'
Step 11: Automate Deployment With GitHub Actions
Manual sam deploy runs are fine for solo projects, but any real application needs deploys triggered by a merge to main. Store AWS credentials as encrypted repository secrets, then add a workflow that builds and deploys on every push.
name: Deploy SAM App
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/setup-sam@v2
with:
use-installer: true
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- run: sam build
- run: sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
For faster iteration during active development, skip the full pipeline and use sam sync --watch instead, which pushes code changes directly to Lambda without a full CloudFormation deploy cycle, cutting a typical iteration loop from minutes down to seconds.
Step 12: Add Observability and Clean Up
Add Tracing: Active under the function’s Globals block to enable AWS X-Ray tracing, and check the Lambda’s automatically created CloudWatch Logs group for structured logs. When you’re done experimenting, tear the whole stack down in one command so nothing keeps running (or billing) in the background.
sam delete --stack-name sam-url-shortener
# confirms deletion of the CloudFormation stack, Lambda function,
# DynamoDB table, IAM role, and API Gateway route in one pass
Bonus: Writing Unit Tests for Your Lambda Function
The sam init scaffold generates a tests/ folder for a reason: testing a Lambda handler as a plain Python function, without invoking SAM or Docker at all, is the fastest feedback loop in the whole workflow. Because lambda_handler takes a plain dictionary and returns a plain dictionary, you can call it directly from pytest and assert on the response the same way you’d test any other function.
# tests/unit/test_handler.py
import json
import os
import boto3
from moto import mock_aws
import pytest
os.environ["TABLE_NAME"] = "test-table"
@pytest.fixture
def dynamodb_table():
with mock_aws():
client = boto3.resource("dynamodb", region_name="us-east-1")
client.create_table(
TableName="test-table",
KeySchema=[{"AttributeName": "shortCode", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "shortCode", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
yield client
def test_shorten_url_returns_201(dynamodb_table):
from shorten_url import app
event = {"body": json.dumps({"url": "https://tech-insider.org"})}
response = app.lambda_handler(event, {})
body = json.loads(response["body"])
assert response["statusCode"] == 201
assert len(body["shortCode"]) == 7
assert body["longUrl"] == "https://tech-insider.org"
def test_shorten_url_rejects_missing_url(dynamodb_table):
from shorten_url import app
response = app.lambda_handler({"body": "{}"}, {})
assert response["statusCode"] == 400
The moto library mocks AWS API calls in-process, so this test suite runs in milliseconds with no Docker container, no network call, and no AWS account required at all. Run it with python -m pytest tests/unit -v as part of your GitHub Actions workflow, before the sam build step, so a broken handler never even reaches the deploy stage. Reserve sam local invoke and the integration tests folder SAM scaffolds for the smaller number of cases where you specifically need to verify IAM permissions, timeout behavior, or the actual API Gateway event shape, since those require the real (or Docker-emulated) Lambda runtime to catch.
Managing Multiple Environments With SAM Config
Every project beyond a personal demo eventually needs separate dev, staging, and production stacks, each with its own DynamoDB table, its own API endpoint, and its own IAM boundaries. SAM handles this through named configuration environments inside a single samconfig.toml, rather than requiring separate template files or a third-party tool.
# samconfig.toml
version = 0.1
[default.deploy.parameters]
stack_name = "sam-url-shortener-dev"
region = "us-east-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
[production.deploy.parameters]
stack_name = "sam-url-shortener-prod"
region = "us-east-1"
confirm_changeset = false
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Stage=prod MemorySize=512"
Deploy to a specific environment by passing --config-env, keeping the same template.yaml and the same codebase across every stage:
# deploys using the [default] block, targeting the dev stack
sam deploy
# deploys using the [production] block, targeting the prod stack
sam deploy --config-env production
Add a Stage parameter to the top of template.yaml with Type: String and a Default: dev, then reference !Ref Stage anywhere you need environment-specific naming, such as appending it to the DynamoDB table name or the CloudWatch log retention period. This keeps a single template.yaml as the source of truth for every environment instead of forking the file per stage, which is the pattern that tends to drift out of sync within a few months on any team larger than one person.
AWS SAM Pricing and the AWS Free Tier in 2026
The SAM CLI itself carries no license fee, per-seat charge, or usage cap. Every dollar you spend running this tutorial’s project comes from the underlying AWS services, and for a small app like the URL shortener above, that number is usually zero. AWS Lambda’s always-free tier applies automatically to any account, resets every month, and doesn’t expire after the standard 12-month new-account window the way some other free-tier offers do.
| Service | Always-Free Monthly Allowance | Cost Beyond Free Tier |
|---|---|---|
| AWS Lambda (requests) | 1,000,000 requests | $0.20 per additional 1M requests (x86); ~20% less on arm64 |
| AWS Lambda (compute) | 400,000 GB-seconds | $0.0000166667 per GB-second beyond the free tier |
| Amazon DynamoDB | 25 GB storage + 200,000,000 requests | Pay-per-request billing beyond free allocation |
| Lambda response streaming | 100 GiB/month (plus first 6 MB/request free) | Standard data transfer rates apply after that |
Put in dollar terms, the Lambda free tier alone is worth roughly $6.87 a month at published on-demand rates, which is enough headroom that most tutorials, side projects, and even small production APIs never generate a bill for compute. The free tier also now explicitly covers ARM/Graviton functions, so setting Architectures: [arm64] in your template, as this tutorial’s template does, doesn’t forfeit any free-tier eligibility and typically runs cheaper once you exceed it.
The bigger cost risk in a real SAM project usually isn’t Lambda or DynamoDB at all, it’s forgetting to run sam delete on throwaway environments. A feature-branch stack spun up for a pull request review and never torn down still accrues DynamoDB storage charges and, if it includes an RDS instance or NAT gateway in its resources, can add up far faster than serverless compute ever would. Wiring sam delete --stack-name pr-${PR_NUMBER} into a GitHub Actions workflow that fires when a pull request closes is a five-line addition that prevents this category of surprise bill entirely.
AWS SAM vs AWS CDK vs Serverless Framework vs Terraform
SAM isn’t the only way to define serverless infrastructure as code, and picking the wrong tool for your team’s workflow is a common source of regret six months in. Here’s how the four most common options actually differ in practice.
| Tool | Language | Best Fit | Local Testing |
|---|---|---|---|
| AWS SAM | YAML/JSON template | AWS-only serverless apps, teams that want native AWS tooling | Built-in (sam local invoke/start-api) |
| AWS CDK | TypeScript, Python, Java, Go, C# | AWS-only infra where you want a real programming language, not YAML | Via CDK Local / SAM CLI integration |
| Serverless Framework | YAML plus a plugin ecosystem | Multi-cloud serverless (AWS, Azure, GCP) with heavy plugin reuse | Via serverless-offline plugin |
| Terraform | HCL | Teams standardizing all infrastructure, not just serverless, on one IaC tool | None built-in; requires localstack or similar |
The practical dividing line is usually this: if your team already manages non-serverless AWS infrastructure in Terraform, introducing SAM as a second tool just for Lambda functions creates state-management friction, since Terraform and CloudFormation don’t share state. In that case, either use Terraform’s own aws_lambda_function resources or reach for AWS CDK, which can generate CloudFormation that still deploys through SAM’s local-testing tooling. If your stack is entirely serverless and entirely AWS, SAM’s tight integration with sam local, sam sync, and native least-privilege policy templates makes it the fastest path from a blank folder to a deployed API, which is the workflow this tutorial just walked through end to end.
Teams that outgrow SAM tend to do so for one of two reasons, and both are worth knowing before you commit. The first is scale of team, not scale of infrastructure: once ten or more engineers are editing the same template.yaml, YAML’s lack of loops, functions, and type checking starts to show, and CDK’s ability to express a “for each environment, create this stack” pattern in actual code becomes worth the switch. The second is portability. A startup that begins entirely on AWS but later needs a GCP or Azure presence, whether for data residency, pricing leverage, or an acquisition, will find that SAM templates don’t translate, while a Serverless Framework or Terraform configuration built with provider abstraction in mind from day one carries over with far less rework. Neither of those situations describes most teams shipping their first serverless API, which is exactly the audience SAM’s guided CLI and local-testing tools are built for.
Common Pitfalls When Working With AWS SAM
- Deploying without rebuilding first. Editing
app.pyand runningsam deploydirectly deploys stale code from the last.aws-sam/buildoutput. Always runsam buildimmediately before every deploy, or usesam deploy --build(available since recent CLI versions) to chain the two. - Overly broad IAM policies. Copy-pasting
Policies: - AdministratorAccessinto a template to “make the permissions error go away” is the single most common security mistake in SAM projects. Use SAM’s built-in policy templates likeDynamoDBCrudPolicyorS3ReadPolicyinstead, which scope access to the exact resource the function needs. - Hardcoding secrets in template.yaml. Database passwords, API keys, and tokens committed as plain
Environment: Variablesvalues end up in your CloudFormation stack’s event history and in git. Reference AWS Secrets Manager or Parameter Store by ARN instead and resolve them at runtime. - Skipping sam validate. A malformed template.yaml often fails deep into a CloudFormation rollback rather than immediately. Running
sam validate --lintbefore every deploy catches most syntax and schema errors in under a second. - Committing the .aws-sam directory to git. This folder is a build artifact, not source code, and can balloon a repository with vendored dependencies. Add it to
.gitignoreon day one. - Ignoring cold starts on latency-sensitive paths. A Python or Node function with a large dependency tree (heavy ML libraries are the usual culprit) can add hundreds of milliseconds of cold-start latency on the first request after idle. For latency-sensitive APIs, consider Lambda SnapStart or provisioned concurrency rather than assuming SAM’s defaults are tuned for your workload.
- Treating samconfig.toml as disposable. This file stores your stack name, region, and deploy parameters. Deleting or ignoring it in git means every teammate has to re-answer the guided prompts, and CI/CD pipelines lose their configuration source of truth.
Advanced Tips for Production-Grade SAM Deployments
Once the basic build-test-deploy loop is comfortable, a handful of newer SAM features are worth adopting for anything beyond a demo. Turn on BuildKit for container-packaged functions by upgrading to SAM CLI 1.159.0 or later and running sam build --use-buildkit; AWS’s own changelog credits it with meaningfully faster image builds through better layer caching, which matters once a container-based function’s build time starts eating into your CI minutes.
For templates that have grown past a handful of resources, adopt CloudFormation Language Extensions, added to SAM in May 2026, to cut down on repeated boilerplate across multiple functions and environments while keeping the same local-invoke and local-API workflow you’ve already built muscle memory for. Use nested stacks or SAM’s AWS::Serverless::Application resource type to break a monolithic template into reusable modules once you’re managing more than three or four functions in one file, since a single sprawling template.yaml becomes hard to review in pull requests.
Finally, wire up sam sync --watch for your local development loop instead of full deploys during active feature work. It detects file changes and pushes only the changed Lambda code or configuration directly to AWS, skipping the CloudFormation changeset entirely, which turns a two-to-three-minute deploy cycle into something closer to five seconds. Reserve full sam deploy runs for merges to your main branch through CI, where a slower, more thorough CloudFormation-backed deploy is exactly what you want.
Troubleshooting AWS SAM CLI Errors
- “Unable to import module ‘app’: No module named ‘boto3′” — Your
requirements.txtwasn’t picked up duringsam build. Confirm it sits inside the sameCodeUrifolder referenced in template.yaml, then rebuild. - “Unable to locate credentials” —
aws configureeither wasn’t run or targets a different named profile than SAM CLI is using. Pass--profile your-profile-nameexplicitly tosam deploy, or check~/.aws/credentials. - Stack stuck in ROLLBACK_COMPLETE — CloudFormation won’t update a stack in this state. Delete it first with
sam delete --stack-name your-stack, then redeploy from scratch. - “User is not authorized to perform: iam:CreateRole” — Your IAM user or role lacks permission to create the execution role SAM needs. Either attach broader permissions temporarily or pre-create the role and reference it with
Role:in the template instead of letting SAM generate one. - sam local invoke hangs or times out — Docker isn’t running, or the Docker daemon can’t pull the base Lambda runtime image. Start Docker Desktop and run
docker psto confirm it’s reachable before retrying. - “Deployment package exceeds the maximum allowed size” — Lambda’s direct upload zip limit is 50 MB (250 MB unzipped). Move large dependencies into a Lambda Layer, or switch the function to container image packaging, which supports images up to 10 GB.
- Template changes not reflected after sam deploy — CloudFormation sometimes reports “No changes to deploy” even after edits if the changeset hash didn’t shift. Run
sam buildagain to force a fresh artifact hash before deploying. - CORS errors calling the API from a browser — HttpApi events need an explicit
Corsconfiguration block; API Gateway doesn’t add permissive CORS headers by default. AddCors: AllowOrigins: ["*"]under the HttpApi properties for local testing, then scope it down for production. - “Resource handler returned message: rate exceeded” during deploy — CloudFormation is being throttled, usually from deploying many stacks in tight succession in the same account. Add a short delay between CI deploy jobs or request a service quota increase.
Complete Working Project: A Serverless URL Shortener API
Putting every step together, the finished project has three files: template.yaml at the root defining the Lambda function, DynamoDB table, and HTTP API route; shorten_url/app.py containing the handler logic; and shorten_url/requirements.txt listing dependencies. The full lifecycle from empty folder to live endpoint looks like this:
sam init # scaffold the project
# (edit template.yaml and app.py as shown above)
sam build # resolve dependencies, prep artifacts
sam local start-api # test on localhost:3000 with Docker
sam deploy --guided # ship it to AWS, save config
curl -X POST $API_URL -d '{"url":"..."}' # confirm the live endpoint works
sam sync --watch # fast-iterate on further changes
sam delete --stack-name sam-url-shortener # tear it all down when finished
From here, natural next steps include adding a GET route to redirect short codes back to their original URL, putting API Gateway behind a custom domain with Route 53, and adding a DynamoDB TTL attribute so short links expire automatically after 90 days. Each of those is a small addition to the same template.yaml pattern established above, not a new tool to learn.
The redirect route, for instance, needs only a second AWS::Serverless::Function block pointed at a new handler that reads the incoming shortCode path parameter, looks it up in the same UrlTable, and returns a 301 response with a Location header set to the stored longUrl. Because both functions share the same DynamoDB table via the same TABLE_NAME environment variable and the same DynamoDBCrudPolicy pattern, adding it is closer to fifteen minutes of work than a new architectural decision. That’s the compounding value of the SAM approach: every additional route, table, or queue is another few lines in the same file and the same sam build / sam deploy loop you already have muscle memory for, rather than a fresh trip through a console wizard.
Frequently Asked Questions
Is AWS SAM free to use?
Yes. The SAM CLI is open-source and free, with no license or per-deploy fee. You only pay for the underlying AWS resources your templates create, and for a small project, Lambda’s permanent free tier of 1 million requests and 400,000 GB-seconds per month typically covers development and light production traffic at no cost.
Do I need Docker to use AWS SAM?
Docker isn’t required to build or deploy, but it is required for local testing with sam local invoke and sam local start-api, since SAM runs your function inside a container that mirrors the actual Lambda execution environment. Skipping local testing and deploying straight to AWS works, but you lose the fastest feedback loop the tool offers.
What’s the difference between AWS SAM and AWS CDK?
SAM defines infrastructure in a YAML or JSON template with shorthand serverless resource types. CDK lets you define the same infrastructure in a general-purpose programming language like TypeScript or Python, which CDK then synthesizes into CloudFormation. SAM has a lower learning curve for pure serverless apps and ships with the more mature local-testing CLI; CDK is a better fit once your infrastructure includes non-serverless resources or you want loops, conditionals, and reusable constructs that YAML struggles to express cleanly.
Can I use AWS SAM with languages other than Python?
Yes. SAM supports every Lambda-managed runtime, including current Node.js, Java, .NET, and Go versions, plus custom runtimes and container images. The sam init wizard prompts for a runtime and generates language-appropriate boilerplate, so the workflow in this tutorial applies regardless of which runtime you choose.
How is sam deploy different from a raw CloudFormation deploy?
sam deploy packages your built code, uploads it to an S3 deployment bucket, and submits a CloudFormation changeset, which is functionally the same mechanism aws cloudformation deploy uses. The difference is that SAM handles the packaging and upload steps automatically and expands SAM’s shorthand resource syntax back into standard CloudFormation before the changeset is created, saving you from writing that translation by hand.
Why does my SAM stack fail with a rollback instead of updating?
CloudFormation rolls back a deploy automatically if any resource in the changeset fails to create or update, most often due to an IAM permissions gap, a naming collision on a resource like an S3 bucket, or a malformed template value. Check the CloudFormation console’s Events tab for the specific resource and error that triggered the rollback rather than guessing from the CLI’s summary output.
Does AWS SAM work for multi-cloud deployments?
No. SAM is AWS-specific by design, built directly on CloudFormation. If you need the same infrastructure definitions to target Azure or Google Cloud alongside AWS, the Serverless Framework or Terraform are better fits, since both support multiple cloud providers through provider plugins rather than being locked to one vendor’s transform engine.
Related Coverage
- How to Deploy AWS ECS Fargate: 12 Steps, 90 Min [2026]
- AWS Well-Architected Framework Review: 12 Steps, 90 Min [2026]
- Deploy a Multiplayer Game Server With AWS GameLift: 12 Steps [2026]
- AWS Systems Manager Azure Setup: 12 Steps, 90 Min [2026]
- AWS Step Functions Terraform Tutorial: 12 Steps, 90 Min [2026]


