sam init && sam build && sam deploy --guided--use-container when your Lambda has native dependencies — builds succeed locally but fail at runtime on Lambda's Amazon Linux 2023.Transform: AWS::Serverless-2016-10-31 — without it, SAM resource types are rejected by CloudFormation.| Setting | Value / Default | Notes |
|---|---|---|
| Runtime | python3.12, nodejs20.x, nodejs22.x, java21 | python3.8–3.11 deprecated 2024+ |
| Handler | app.lambda_handler (Python), app.handler (Node) | Relative to CodeUri |
| CodeUri | ./src/ | Path to function code |
| MemorySize | 128 MB (default), max 10240 MB | CPU scales linearly with memory |
| Timeout | 3 sec (default), max 900 sec | Set realistically |
| Architectures | x86_64 (default) or arm64 | arm64 (Graviton2) is 20% cheaper |
| Tracing | Active or PassThrough | Active enables X-Ray |
| AutoPublishAlias | live | Enables gradual deploy |
| Environment.Variables | key-value map | Use SSM refs for secrets |
| Layers | list of Layer ARNs | Max 5 layers per function |
| Events.Type | Api (REST v1) or HttpApi (v2) | HTTP API is cheaper and faster |
| Events.Path | /items/{id} | Supports path parameters |
| Events.Method | get, post, put, delete | Case-insensitive |
| Globals.Function | Shared defaults | Override per-function as needed |
START
├── Need local testing with hot-reload?
│ ├── YES → Use `sam local start-api --warm-containers EAGER`
│ └── NO ↓
├── Function has native C/C++ dependencies?
│ ├── YES → Always use `sam build --use-container`
│ └── NO ↓
├── Multiple functions sharing code?
│ ├── YES → Create a Lambda Layer (AWS::Serverless::LayerVersion)
│ └── NO ↓
├── Need WebSocket or real-time?
│ ├── YES → Use AWS::ApiGatewayV2::Api with $connect/$disconnect
│ └── NO ↓
├── Need <$3.50/million requests and <30s latency OK?
│ ├── YES → Use HttpApi (v2) — cheaper, auto-CORS, JWT auth built-in
│ └── NO ↓
├── Need request validation, API keys, usage plans?
│ ├── YES → Use Api (REST v1) — full API Gateway feature set
│ └── NO ↓
└── DEFAULT → Use HttpApi (v2) for most new projects
Create a project from an official starter template. [src1]
sam init --runtime python3.12 --app-template hello-world --name my-api
cd my-api
Verify: ls template.yaml → expected: file exists with Transform: AWS::Serverless-2016-10-31
Write the SAM template with a Lambda function behind API Gateway. [src2] [src3]
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: My API - Lambda + API Gateway
Globals:
Function:
Timeout: 30
MemorySize: 256
Runtime: python3.12
Architectures:
- arm64
Parameters:
Stage:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
Resources:
GetItemsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.get_items
Events:
GetItems:
Type: HttpApi
Properties:
Path: /items
Method: get
CreateItemFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.create_item
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref ItemsTable
Events:
CreateItem:
Type: HttpApi
Properties:
Path: /items
Method: post
ItemsTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
Verify: sam validate → expected: template.yaml is a valid SAM Template
Compile dependencies and prepare deployment artifacts. [src4]
# Standard build
sam build
# For native dependencies:
sam build --use-container
Verify: ls .aws-sam/build/ → expected: one directory per function
Run the API locally before deploying. [src1]
sam local start-api --warm-containers EAGER
# In another terminal:
curl http://127.0.0.1:3000/items
Verify: curl http://127.0.0.1:3000/items → expected: JSON response
Deploy with guided mode on first run. [src5]
# First deploy
sam deploy --guided
# Subsequent deploys
sam deploy
Verify: aws cloudformation describe-stacks --stack-name my-api --query 'Stacks[0].Outputs' → expected: ApiUrl output
# Input: API Gateway event (HttpApi v2 format)
# Output: HTTP response dict with statusCode, headers, body
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def handler(event, context):
logger.info(f"Request: {event['requestContext']['http']['method']} "
f"{event['rawPath']}")
path = event.get('rawPath', '/')
method = event['requestContext']['http']['method']
if path == '/health' and method == 'GET':
return {
'statusCode': 200,
'body': json.dumps({'status': 'healthy',
'remaining_ms': context.get_remaining_time_in_millis()})
}
return {'statusCode': 404, 'body': json.dumps({'error': 'Not found'})}
// Input: API Gateway event (HttpApi v2 format)
// Output: HTTP response object
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb');
const client = new DynamoDBClient({});
const ddb = DynamoDBDocumentClient.from(client);
exports.handler = async (event) => {
const id = event.pathParameters?.id;
if (!id) {
return { statusCode: 400, body: JSON.stringify({ error: 'Missing id' }) };
}
try {
const { Item } = await ddb.send(new GetCommand({
TableName: process.env.TABLE_NAME,
Key: { id }
}));
if (!Item) {
return { statusCode: 404, body: JSON.stringify({ error: 'Not found' }) };
}
return { statusCode: 200, body: JSON.stringify(Item) };
} catch (err) {
console.error('DDB error:', JSON.stringify({ error: err.message, id }));
return { statusCode: 500, body: JSON.stringify({ error: 'Internal error' }) };
}
};
# ❌ BAD — single function handles all routes, over-permissioned
def handler(event, context):
path = event['rawPath']
if path == '/users': return handle_users(event)
elif path == '/orders': return handle_orders(event)
elif path == '/payments': return handle_payments(event)
# 50 more routes...
# ✅ GOOD — separate functions with targeted permissions
Resources:
UsersFunction:
Type: AWS::Serverless::Function
Properties:
Handler: users.handler
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
GetUsers:
Type: HttpApi
Properties: { Path: /users, Method: get }
# ❌ BAD — secrets visible in CloudFormation and version control
Environment:
Variables:
DB_PASSWORD: "my-secret-password-123"
# ✅ GOOD — resolved at deploy time, not stored in template
Environment:
Variables:
DB_PASSWORD: '{{resolve:ssm-secure:/myapp/db-password}}'
# ❌ BAD — duplicating config across every function
Resources:
Func1:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.12
Timeout: 30
MemorySize: 256
# ... repeated for every function
# ✅ GOOD — shared config in one place
Globals:
Function:
Runtime: python3.12
Timeout: 30
MemorySize: 256
Architectures: [arm64]
sam build --use-container for packages with C extensions. [src4]Action: '*'. Fix: use SAM policy templates like DynamoDBCrudPolicy, S3ReadPolicy. [src6]# Validate SAM template
sam validate --lint
# View function logs (tail mode)
sam logs --name GetItemsFunction --stack-name my-api --tail
# Check function configuration
aws lambda get-function-configuration --function-name my-api-GetItemsFunction-abc123
# Test local invocation
sam local invoke GetItemsFunction --event events/get.json --debug
# Check deployment status
aws cloudformation describe-stack-events --stack-name my-api --max-items 10
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| SAM CLI 1.100+ | Current | Unified build behavior | Use --build-in-source for legacy |
| Python 3.12 | Current | None | Recommended runtime |
| Node.js 22.x | Current | ESM default | Use type: module in package.json |
| Node.js 20.x | LTS until Apr 2026 | — | AWS SDK v3 required |
| HTTP API (v2) | Current | — | Preferred for new projects |
| REST API (v1) | Stable | — | Use when v1-only features needed |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Building REST/HTTP APIs with <15 min execution | Long-running tasks >15 min | ECS Fargate, Step Functions |
| Event-driven processing (S3, SQS, DynamoDB Streams) | Sustained high throughput (>1000 req/s constant) | ECS/EKS with ALB |
| Prototyping and MVPs | Complex stateful applications | EC2, ECS with persistent storage |
| Infrequent or bursty workloads | GPU/ML inference workloads | SageMaker endpoints |
| Team already uses CloudFormation | Team prefers Terraform | Terraform AWS provider |
sam local uses Docker containers that approximate but don't perfectly replicate the Lambda execution environment — always test in a dev stage.