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