Skip to main content

AWS SAM for Python Lambda: Infrastructure as Code

AWS SAM (Serverless Application Model) is a framework for defining serverless applications in YAML or JSON. Instead of manually configuring Lambda, API Gateway, and event sources in the AWS Console, you declare your infrastructure in code, version-control it, and deploy reproducibly via CloudFormation. SAM is ideal for building, testing, and deploying production serverless applications.

What is AWS SAM and Why Use Infrastructure as Code?​

SAM is an open-source framework that extends AWS CloudFormation with serverless-specific resources. You write a template.yaml file describing your functions, APIs, databases, and permissions. SAM transforms this into CloudFormation resources and deploys them to AWS.

Benefits:

  • Reproducibility: Deploy identical infrastructure to dev, staging, and production.
  • Version control: Track infrastructure changes in Git like application code.
  • Automation: Deploy via CI/CD pipelines; no manual console clicks.
  • Documentation: The template is a single source of truth for your architecture.
  • Cost tracking: Estimate costs before deployment using CloudFormation cost estimator.

Install AWS SAM and Create a Project​

Install the AWS SAM CLI:

# macOS via Homebrew
brew tap aws/tap
brew install aws-sam-cli

# Windows via Chocolatey
choco install aws-sam-cli

# Or download from https://aws.amazon.com/serverless/sam/
sam --version
# Output: SAM CLI, version 1.80.0

Create a new SAM project:

sam init --runtime python3.12 --application-template hello-world

# Output:
# Which template source would you like to use?
# 1. AWS Serverless Application Repository
# ... (choose 1)
# ... SAM will scaffold a project structure

The scaffolded project includes:

my-app/
template.yaml # Infrastructure definition
hello_world/
app.py # Lambda function code
requirements.txt # Python dependencies
tests/
unit/
test_app.py # Unit tests
events/
event.json # Sample invocation event
.gitignore
README.md

Understanding the SAM Template Structure​

The template.yaml defines your infrastructure:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'Serverless Python API'

Globals:
Function:
Timeout: 30
MemorySize: 128
Runtime: python3.12
Handler: app.lambda_handler
Tracing: Active

Resources:
# Lambda function
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Timeout: 30
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: GET
RestApiId: !Ref ServerlessApi

# API Gateway
ServerlessApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Auth:
DefaultAuthorizer: ~

# DynamoDB table
UserTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: users-table
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH

Outputs:
ApiEndpoint:
Description: 'API Gateway endpoint URL'
Value: !Sub 'https://${ServerlessApi}.execute-api.${AWS::Region}.amazonaws.com/prod'
FunctionArn:
Description: 'Lambda function ARN'
Value: !GetAtt HelloWorldFunction.Arn
TableName:
Description: 'DynamoDB table name'
Value: !Ref UserTable

Defining a Lambda Function with SAM​

Create a simple Python function:

# hello_world/app.py
import json

def lambda_handler(event, context):
try:
path = event.get('path', '/')

return {
'statusCode': 200,
'body': json.dumps({
'message': 'Hello from Lambda!',
'path': path
}),
'headers': {'Content-Type': 'application/json'}
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}

Define it in SAM:

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/ # Directory containing app.py
Handler: app.lambda_handler # Function entry point
Runtime: python3.12
Timeout: 30
MemorySize: 256 # Memory allocation
Environment:
Variables:
ENVIRONMENT: production
TABLE_NAME: !Ref UserTable # Reference other resources
Policies: # IAM permissions
- DynamoDBCrudPolicy:
TableName: !Ref UserTable
- CloudWatchPutMetricPolicy: {}
Events:
ApiEvent:
Type: Api
Properties:
RestApiId: !Ref ServerlessApi
Path: /hello
Method: GET

Adding Event Sources (S3, DynamoDB, SQS)​

Define event sources in the Events section of your function:

S3 Trigger:

Resources:
S3ProcessorFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3_processor/
Handler: app.lambda_handler
Events:
S3Event:
Type: S3
Properties:
Bucket: !Ref ImageBucket
Events: s3:ObjectCreated:*
Filter:
S3Key:
Rules:
- Name: prefix
Value: uploads/
- Name: suffix
Value: .jpg

ImageBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'my-images-${AWS::AccountId}'

DynamoDB Stream:

Resources:
StreamProcessorFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: stream_processor/
Handler: app.lambda_handler
Events:
DynamoDBEvent:
Type: DynamoDB
Properties:
Stream: !GetAtt UserTable.StreamArn
StartingPosition: TRIM_HORIZON
BatchSize: 100

UserTable:
Type: AWS::DynamoDB::Table
Properties:
StreamSpecification:
StreamViewType: NEW_AND_OLD_IMAGES
...

SQS Queue:

Resources:
QueueProcessorFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: queue_processor/
Handler: app.lambda_handler
Events:
SQSEvent:
Type: SQS
Properties:
Queue: !GetAtt MyQueue.Arn
BatchSize: 10

MyQueue:
Type: AWS::SQS::Queue
Properties:
VisibilityTimeout: 300

Build and Deploy with SAM​

Build your application (downloads dependencies, validates syntax):

sam build

# Output:
# Building resources
# Running HelloWorldFunction
# Build Succeeded
# Built Artifacts : .aws-sam/build
# Built Template : .aws-sam/build/template.yaml

Deploy to AWS:

sam deploy --guided

# First deployment prompts for parameters:
# Stack Name [sam-app]: my-serverless-app
# AWS Region [us-east-1]: us-east-1
# Confirm changes before deploy [Y/n]: Y
# Allow SAM CLI IAM role creation [Y/n]: Y
# HelloWorldFunction may not have authorization defined. Is this OK? [Y/n]: Y
# Save parameters to samconfig.toml [Y/n]: Y
# Deploy this changeset? [Y/n]: Y

# Output:
# Stack creation succeeded with IAM_ROLE permissions
# Resources created: 5
# Outputs:
# ApiEndpoint: https://abc123.execute-api.us-east-1.amazonaws.com/prod
# FunctionArn: arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction

Subsequent deployments use the saved configuration:

sam deploy
# Uses parameters from samconfig.toml; no prompts

Testing Locally with SAM​

Test your function locally before deploying:

# Invoke a function
sam local invoke HelloWorldFunction -e events/event.json

# Output:
# 2026-06-02T10:30:00 Loading function code from hello_world
# 2026-06-02T10:30:00 Initializing Runtime
# 2026-06-02T10:30:00 Function loaded
# START RequestId: ...
# Hello from Lambda!
# END RequestId: ...

Test your API locally:

sam local start-api

# Output:
# Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
# You can now browse to http://127.0.0.1:3000/hello

# In another terminal:
curl http://127.0.0.1:3000/hello
# {"message": "Hello from Lambda!", "path": "/hello"}

Managing Parameters and Environment Variables​

Use Parameters for configuration that changes between environments:

Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
TableName:
Type: String
Default: users-table

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
ENVIRONMENT: !Ref Environment
TABLE_NAME: !Ref TableName

UserTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub '${TableName}-${Environment}'
...

Deploy with parameters:

sam deploy --parameter-overrides Environment=prod TableName=production-users

Cleanup​

Remove all AWS resources created by SAM:

sam delete

# Prompt:
# Are you sure you want to delete the stack my-serverless-app in the region us-east-1? [Y/n] Y
# Are you sure you want to delete the S3 bucket aws-cloudformation-bucket? [Y/n] Y
# Delete complete

This prevents accidental AWS charges.

Key Takeaways​

  • AWS SAM is a framework for defining serverless applications in YAML; sam build and sam deploy handle packaging and CloudFormation.
  • Define Lambda functions, event sources, databases, and permissions in template.yaml; version-control this as your infrastructure specification.
  • Use sam local invoke and sam local start-api to test functions locally before deploying.
  • Parameters enable environment-specific configurations (dev/staging/prod) from a single template.
  • Outputs expose important resource identifiers (API endpoints, function ARNs) for downstream use.
  • SAM validates syntax and permissions automatically, reducing deployment errors.

Frequently Asked Questions​

Can I convert existing CloudFormation templates to SAM?​

SAM is a superset of CloudFormation. Most CloudFormation resources work in SAM templates as-is. To use SAM-specific features (like AWS::Serverless::Function), rewrite those resources. AWS provides a migration guide: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html.

How do I add secrets (API keys, database passwords) to my SAM template?​

Never hardcode secrets in templates. Use AWS Secrets Manager and reference it:

Resources:
MyFunction:
Properties:
Environment:
Variables:
DB_PASSWORD: !Sub '{{resolve:secretsmanager:db-secret:SecretString:password}}'

Can I deploy SAM with CI/CD?​

Yes. Store template.yaml in Git. Your CI pipeline runs sam build && sam deploy on every commit. This automates infrastructure deployments alongside code.

What's the difference between SAM and Terraform?​

SAM is AWS-specific and simpler for serverless; Terraform is cloud-agnostic and more powerful for complex multi-cloud infrastructure. For AWS-only serverless apps, SAM is ideal.

How do I monitor my SAM-deployed functions?​

CloudWatch Logs and Metrics are automatically integrated. Enable AWS X-Ray tracing in the template: set Tracing: Active in the Globals section. View traces in the X-Ray console.

Further Reading​