This repository ships a minimal, enterprise-friendly starter that:
- Builds a single Docker image for ARM64 (native on Apple Silicon) that runs as an AWS Lambda and can also run a tiny local Python HTTP service.
- Lets you test Lambda locally using the official Runtime Interface Emulator (RIE) built into the AWS Lambda Python base image.
- Deploys entirely with CloudFormation (ECR repo, IAM role, Lambda from container image, Function URL).
- Uses AWS CLI SSO (IAM Identity Center) for authentication.
- Keeps application deps in
requirements.in(compiled to pinnedrequirements.txtat image build).
- Prerequisites
- AWS SSO (one-time per account)
- Project Layout
- .env Configuration
- Local Build & Run (Apple Silicon / ARM64)
- Provision AWS Infrastructure (CloudFormation)
- Build, Tag & Push the Image to ECR
- Deploy the Lambda + Function URL (CloudFormation)
- Test on AWS
- Cleanup
- Troubleshooting
- References
- Docker Desktop (with Buildx) on macOS Apple Silicon (M1/M2/M3).
- AWS CLI v2 installed.
- Make installed.
- Access to an AWS account via IAM Identity Center (SSO).
The project uses the official AWS Lambda Python base image, which mirrors the Lambda environment and includes the Runtime Interface Emulator for local testing.
Configure an SSO-backed CLI profile and sign in:
aws configure sso
# Follow the prompts: SSO start URL, SSO region, account, role, and profile name.
aws sso login --profile <your-sso-profile>
aws sts get-caller-identity --profile <your-sso-profile>aws configure ssocreates a profile that sources temporary credentials from IAM Identity Center. (AWS Documentation)aws sso loginretrieves and caches an SSO access token for that profile. (AWS CLI)
.
├── app/
│ ├── __init__.py
│ ├── handler.py # Lambda handler
│ └── service.py # Tiny FastAPI "service" for local health/echo
├── cfn/
│ ├── infra.yaml # ECR repo + IAM role
│ └── app.yaml # Lambda from container image (arm64) + Function URL
├── .dockerignore
├── .env.example
├── Dockerfile
├── Makefile
├── requirements.in # single source of truth
└── requirements.txt # pinned (compiled at build)
app/handler.py— Lambda handler that returns a JSON response with event data and an environment variable.app/service.py— FastAPI app with/healthand/echoendpoints for local testing.Dockerfile— Multi-purpose image using AWS Lambda Python 3.11 base image.Makefile— All commands for build, deploy, and test workflows.cfn/infra.yaml— CloudFormation template for ECR repository and Lambda execution role.cfn/app.yaml— CloudFormation template for Lambda function and Function URL.
Copy and edit:
cp .env.example .envKey variables (override as needed):
# ---- General ----
APP_NAME=lambda-python-basic
AWS_REGION=us-west-2
AWS_PROFILE=your-sso-profile-name
ECR_REPO_NAME=lambda-python-basic
IMAGE_TAG=v0
# ---- App runtime (used locally and in AWS) ----
EXAMPLE_MESSAGE=Hello from .env (local) and CloudFormation (AWS)
# ---- Stacks ----
STACK_INFRA=lambda-python-basic-infra
STACK_APP=lambda-python-basic-appLocal vs AWS env
- Local:
makeloads.envand passes variables to containers. - AWS:
cfn/app.yamlsetsEnvironment.Variableson the Lambda function; these are standard Lambda env vars. For secrets, prefer AWS Secrets Manager or SSM Parameter Store (not included in this minimal starter). (AWS Documentation)
make build- The Makefile builds with
--platform linux/arm64to match the Lambda architecture and run natively on M1/M2/M3.
make run-lambda-local
# In another terminal:
make invoke-local- The AWS base image includes the Runtime Interface Emulator, which exposes a local endpoint on
:9000. We invoke the standard Lambda Runtime API path to simulate an invocation. (GitHub) - Expected response:
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"message\": \"Hello from .env (local) and CloudFormation (AWS)\", \"received_event\": {\"ping\": \"pong\"}, \"arch_hint\": \"arm64\"}"
}make run-api
# Visit http://localhost:8000/health and http://localhost:8000/echo?msg=hiAll AWS resources are defined as code in cfn/.
make cfn-up-infra- Creates a private ECR repository (immutability, scan-on-push) and a Lambda execution role with basic logging.
- Outputs the ECR repo URI and role ARN for later steps.
ECR hosts the container image used by the Lambda; CloudFormation is the supported way to define the Lambda function and its image reference (
Code.ImageUri). (AWS Documentation)
make ecr-loginThis uses the recommended get-login-password flow with Docker's login command. (AWS Documentation)
make pushTags your local image with the ECR URI exported by the infra stack and pushes :$(IMAGE_TAG).
make cfn-up-appThis stack:
- Creates/updates AWS::Lambda::Function with
PackageType: Imageand your ECRImageUri. (AWS Documentation) - Sets
Architectures: ["arm64"]to run on Graviton/ARM64. - Creates an AWS::Lambda::Url for quick HTTPS testing (public in this starter; switch to
AWS_IAMfor private). (AWS Documentation)
The official Lambda container-image flow requires referencing an image in Amazon ECR via
Code.ImageUriwhen using CloudFormation. (AWS Documentation)
make invoke-awsReturns the handler's JSON response for a sample payload:
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"message\": \"Hello from .env (local) and CloudFormation (AWS)\", \"received_event\": {\"from\": \"make\"}, \"arch_hint\": \"arm64\"}"
}FUNCTION_URL=$(make -s show-url)
curl -s "$FUNCTION_URL" | python -m json.tool- A Function URL is a dedicated HTTPS endpoint to invoke your function directly. In this starter it's public (
AuthType: NONE) for convenience; for production useAWS_IAMand a matchingAWS::Lambda::Permission. (AWS Documentation)
make teardown- Deletes the app and infra stacks. You may need to delete images/tags to remove an immutable ECR repo cleanly.
Re-run aws sso login --profile <your-sso-profile> to refresh tokens. Ensure the profile was created with aws configure sso. (AWS Documentation)
Use the get-login-password flow and verify your AWS CLI is up to date. (AWS Documentation)
# Verify AWS CLI version
aws --version
# Should be 2.xEnsure the container is running and you're POSTing to /2015-03-31/functions/function/invocations on port 9000. The RIE is included in the AWS Lambda base image for local testing. (GitHub)
# Check if container is running
docker ps
# Check logs
docker logs <container-id>Always build with --platform linux/arm64 locally and set Architectures: ["arm64"] in CloudFormation for the function. (AWS Documentation)
If you see errors about empty ECR_URI or ROLE_ARN, ensure you've run make cfn-up-infra first and the stack has completed successfully.
# Check stack status
aws cloudformation describe-stacks --stack-name lambda-python-basic-infra --profile <your-sso-profile>make help # Show all available commands
make build # Build ARM64 image
make run-lambda-local # Run Lambda locally on :9000 with RIE
make invoke-local # Invoke local Lambda
make run-api # Run FastAPI service on :8000
make cfn-up-infra # Deploy ECR repo + IAM role
make ecr-login # Docker login to ECR
make push # Build/tag/push image to ECR
make cfn-up-app # Deploy Lambda (container image) + Function URL
make invoke-aws # Invoke deployed Lambda via AWS CLI
make show-url # Print Function URL
make teardown # Delete both stacks- AWS Lambda (Python) container images & local testing — base images and usage. (AWS Documentation)
- Runtime Interface Emulator (RIE) — local testing for containerized Lambdas. (GitHub)
- CloudFormation: AWS::Lambda::Function —
PackageType: Image,Code.ImageUri,Architectures. (AWS Documentation) - CloudFormation: AWS::Lambda::Url — Function URL resource. (AWS Documentation)
- Amazon ECR auth —
get-login-password+ Docker login. (AWS Documentation) - AWS CLI SSO Configuration — configuring IAM Identity Center authentication. (AWS Documentation)
This project is open source and available under the MIT License.