Accelerate FastAPI Development on AWS Lambda with Lambda Web Adapter
Introduction
Lambda Web Adapter has revolutionized application development on AWS Lambda. It allows developers to build backend services using Docker, a tool familiar to most developers.
This post guides you through the process of developing API backends with FastAPI using Lambda Web Adapter. You can clone the example from my GitHub repository.
Overview
Merits of Lambda Web Adapter
Lambda Web Adapter offers several advantages over traditional AWS Lambda:
Comparison Items | Lambda Web Adapter | Traditional Lambda |
---|---|---|
Testing locally | Easy | Hard |
Migrating to other services (e.g., Fargate) | Easy | Hard |
Number of AWS resources | Less1 | More |
Cost | Low | Very low |
1 With Lambda Web Adapter, you only need one Lambda function and one API Gateway route.
AWS Design
Traditional Design
The traditional AWS design using API Gateway and multiple Lambda functions can become unwieldy when managing many APIs:
Design Using Lambda Web Adapter
Lambda Web Adapter simplifies the architecture, requiring only a single API Gateway route and one Lambda function:
Project Structure
This post creates the following project structure:
/
|-- .venv/
|-- cdk/
| |-- bin/
| | `-- cdk.ts
| |-- lib/
| | `-- cdk-stack.ts
| |-- node_modules/
| |-- test/
| | `-- cdk.test.ts
| |-- .gitignore
| |-- .npmignore
| |-- biome.json
| |-- cdk.json
| |-- jest.config.js
| |-- package.json
| |-- package-lock.json
| |-- README.md
| `-- tsconfig.json
|-- docker/
| |-- Dockerfile-web
| `-- compose.yaml
|-- node_modules/
|-- src/
| |-- main.py
| `-- requirements.txt
|-- .gitignore
|-- package.json
`-- package-lock.json
Getting Started
Bootstrapping AWS CDK Environment
First, bootstrap your AWS CDK environment.
Follow the AWS CDK guide to install AWS CDK locally:
npm i -D aws-cdk
npx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>
Initializing a CDK Project
To initialize your CDK project, run the following command:
mkdir cdk && cd cdk
npx cdk init app --language typescript
Setting up FastAPI
Install FastAPI with the following commands:
python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
mkdir src
pip freeze > ./src/requirements.txt
Backend API using FastAPI
Writing an API
Here’s an example FastAPI application saved to ./src/main.py
:
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
Testing the API
Start the FastAPI server with:
fastapi dev ./src/main.py
Test the API with your preferred tool:
curl "http://127.0.0.1:8000/"
{"Hello":"World"}
curl "http://127.0.0.1:8000/items/1?q=keyword"
{"item_id":1,"q":"keyword"}
Containerizing using Docker
Dockerfile
To containerize the FastAPI backend, write the following code and save it to ./docker/Dockerfile-web
. This Dockerfile is designed to handle both development and production environments effectively.
Key points:
- Base Image: We use
public.ecr.aws/docker/library/python:3.12-alpine
as our lightweight and secure base image (line 2). - Lambda Web Adapter: The Lambda Web Adapter is added for seamless AWS Lambda integration (line 22).
- Port Configuration: The backend listens on port 8080 by default for production to match the Lambda Web Adapter’s expected configuration (line 25).
For more details on Lambda Web Adapter usage, refer to its official documentation.
# Base image: Python 3.12 Alpine
FROM public.ecr.aws/docker/library/python:3.12-alpine AS base
ENV APP_ROOT=/code
# Copy requirements and install dependencies
COPY ./src/requirements.txt $APP_ROOT/
RUN pip install --no-cache-dir --upgrade -r $APP_ROOT/requirements.txt
# Development stage
FROM base AS dev
ENV ENV=dev
EXPOSE 8000
CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8000"]
# Production stage
FROM base
ENV ENV=prod
EXPOSE 8080
COPY ./src $APP_ROOT
# Copy Lambda Web Adapter
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
# Run FastAPI backend on port 8080 for Lambda Web Adapter
CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8080"]
In the next step, we will set up Docker Compose to streamline local development and testing.
Docker Compose for Local Development
While this post does not include database usage, in real-world scenarios, you often work with databases such as DynamoDB, MySQL, or others. To facilitate local development and testing, we can set up Docker Compose. This configuration ensures that your API backend runs smoothly in an isolated environment.
Create a ./docker/compose.yaml
file with the following content:
services:
api:
build:
context: ../
dockerfile: ./docker/Dockerfile-web
target: dev
ports:
- "8000:8000"
volumes:
- ../src:/code
Key Configuration Details
- Build Context: Points to the project root (
../
) and uses the development stage of the Dockerfile (target: dev
). - Ports Mapping: Maps the container’s port
8000
to the host’s port8000
for local access. - Volumes: Mounts the local
src
directory to the container’s/code
directory to enable hot-reloading of code during development.
Running the Backend Service
To start the backend service locally using Docker Compose, run the following commands:
cd docker
docker compose up
When the service starts successfully, you should see logs similar to:
api-1 | INFO: Started server process [1]
api-1 | INFO: Waiting for application startup.
api-1 | INFO: Application startup complete.
api-1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
You can now access the API at http://127.0.0.1:8000 and test it as usual using tools like curl
, Postman, or a browser.
Next, we will explore deploying the FastAPI application to AWS Lambda using AWS CDK.
Deploying to AWS
Defining AWS Resources using AWS CDK
To deploy the FastAPI backend to AWS, define the necessary resources using AWS CDK. The CDK simplifies resource creation by allowing you to define infrastructure in code.
Step 1: Configure the Entry Point
Paste the following code into cdk/bin/cdk.ts
. This is the entry point for your AWS CDK application.
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkStack } from '../lib/cdk-stack';
const app = new cdk.App();
new CdkStack(app, 'App');
Step 2: Define the Infrastructure Stack
In cdk/lib/cdk-stack.ts
, define the AWS Lambda function and API Gateway. The following code creates a Dockerized Lambda function and integrates it with an API Gateway REST API.
import * as cdk from 'aws-cdk-lib';
import type { Construct } from 'constructs';
import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway';
import {
DockerImageCode,
DockerImageFunction,
LoggingFormat,
} from 'aws-cdk-lib/aws-lambda';
import * as path from 'node:path';
import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
export class CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Dockerized Lambda Function
const lambda = new DockerImageFunction(this, 'function', {
functionName: 'fast-api-app-function',
loggingFormat: LoggingFormat.JSON,
memorySize: 512, // Larger memory to avoid timeout
code: DockerImageCode.fromImageAsset(path.join(__dirname, '..', '..'), {
file: path.join('docker', 'Dockerfile-web'),
platform: Platform.LINUX_AMD64, // Required for Apple Silicon users
exclude: ['*', '!src', '!docker'],
}),
});
// API Gateway REST API
new LambdaRestApi(this, 'api', {
handler: lambda,
deploy: true,
});
}
}
Keynotes
- Memory Size: Set
memorySize
to 512 MB or higher (line 20). This prevents potential timeouts caused by insufficient memory. - Platform Configuration: Use
Platform.LINUX_AMD64
(line 23) if you are using Apple Silicon. Without this, you may encounter the error:Error: fork/exec /opt/extensions/lambda-adapter: exec format error Extension.LaunchError
.
With these files in place, you’re ready to deploy your resources.
Step 3: Deploy the Resources
Navigate to your CDK project directory and run the following command to deploy:
cd cdk
npx cdk deploy
During deployment, you may be prompted to confirm resource creation. Respond with y
to proceed:
Do you wish to deploy these changes (y/n)? y
App: deploying... [1/1]
App: creating CloudFormation changeset...
✅ App
✨ Deployment time: 52.67s
Outputs:
App.apiEndpoint9349E63C = https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
Stack ARN:
arn:aws:cloudformation:<AWS_REGION>:<AWS_ACCOUNT_ID>:stack/App/<UUID>
✨ Total time: 55.42s
The deployment will output the API Gateway endpoint, which you can use to test your API.
Testing the App
Test the deployed APIs:
curl "https://<API_GATEWAY_ENDPOINT>/prod/"
{"Hello":"World"}
curl "https://<API_GATEWAY_ENDPOINT>/prod/items/1?q=keyword"
{"item_id":1,"q":"keyword"}
Conclusion
Lambda Web Adapter significantly simplifies API development on AWS Lambda by reducing complexity and facilitating local testing capabilities. With its ability to streamline AWS architectures, it is an invaluable tool for modern web development.
Happy Coding! 🚀