Deploying FastAPI on AWS Lambda with Lambda Web Adapter

Deploying FastAPI on AWS Lambda with Lambda Web Adapter

Containerizing a FastAPI backend and deploying it to a single Lambda function with Lambda Web Adapter and AWS CDK.

Takahiro Iwasa
6 min read

This example containerizes an API backend written with FastAPI and deploys it to AWS Lambda using Lambda Web Adapter.

The traditional AWS design using API Gateway and multiple Lambda functions can become unwieldy when managing many APIs.

Lambda Web Adapter simplifies the architecture, requiring only a single API Gateway route and one Lambda function.

Getting Started

Bootstrapping AWS CDK Environment

First, bootstrap your AWS CDK environment. If you’ve already done this, you can skip this step.

Run the following commands to install AWS CDK locally and bootstrap your environment:

Terminal window
npm i -D aws-cdk
npx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>

Initializing CDK Project

Create a directory for your CDK project and initialize it:

Terminal window
mkdir cdk && cd cdk
npx cdk init app --language typescript

Installing FastAPI

Install FastAPI with the following commands:

Terminal window
python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
mkdir src
pip freeze > ./src/requirements.txt

Building Backend

Defining APIs

Here’s an example FastAPI application:

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}

Start the FastAPI server with:

Terminal window
fastapi dev ./src/main.py

Test the API with your preferred tool:

Terminal window
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

To containerize the FastAPI backend, write the ./docker/Dockerfile. This Dockerfile is designed to handle both development and production environments effectively.

  • 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 GitHub repository.

docker/Dockerfile
# 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"]

(Optional) Docker Compose

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.

  • Build Context: Points to the project root (../) (line 4).
  • Build Target: Uses the development stage of the Dockerfile (target: dev) (line 6).
  • Ports Mapping: Maps the container’s port 8000 to the host’s port 8000 for local access (line 8).
  • Volume: Mounts the local src directory at /code in the container so code changes are available during local development (line 10).
docker/compose.yaml
services:
api:
build:
context: ../
dockerfile: ./docker/Dockerfile
target: dev
ports:
- "8000:8000"
volumes:
- ../src:/code

To start the backend service locally using Docker Compose, run the following commands:

Terminal window
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.

Deploying to AWS

Defining AWS Resources

To deploy the FastAPI backend to AWS, define the necessary resources using AWS CDK.

cdk/bin/cdk.ts
#!/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');
  • Memory Size: This example allocates 512 MB (line 20). Memory also determines CPU allocation in Lambda, so tune this value after measuring startup time and workload behavior.
  • Platform Configuration: Platform.LINUX_AMD64 (line 23) makes the image architecture match the function’s default x86_64 architecture, including when the image is built on Apple Silicon. An architecture mismatch can cause Error: fork/exec /opt/extensions/lambda-adapter: exec format error Extension.LaunchError.
cdk/lib/cdk-stack.ts
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, // Tune after measuring the workload
code: DockerImageCode.fromImageAsset(path.join(__dirname, '..', '..'), {
file: path.join('docker', 'Dockerfile'),
platform: Platform.LINUX_AMD64, // Match Lambda's default x86_64 architecture
exclude: ['*', '!src', '!docker'],
}),
});
// API Gateway REST API
new LambdaRestApi(this, 'api', {
handler: lambda,
deploy: true,
});
}
}

Deploying the Stack

Navigate to your CDK project directory and run the following command to deploy:

Terminal window
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 APIs

Test the deployed APIs:

Terminal window
curl "https://<API_GATEWAY_ENDPOINT>/prod/"
{"Hello":"World"}
curl "https://<API_GATEWAY_ENDPOINT>/prod/items/1?q=keyword"
{"item_id":1,"q":"keyword"}

Conclusion

Containerizing a FastAPI app with Lambda Web Adapter and deploying it via CDK put the entire API behind a single Lambda function and one API Gateway route.

Lambda Web Adapter’s real contribution is letting FastAPI’s own routing replace what would otherwise be a growing pile of individual Lambda functions and API Gateway route mappings — the whole application ships as one container behind a single proxy route, so adding an endpoint is a code change rather than an infrastructure change.

The two CDK settings should be treated as explicit choices rather than universal requirements. The 512 MB memory allocation is a starting point to benchmark, while Platform.LINUX_AMD64 ensures that an image built on an Apple Silicon host still matches the function’s default x86_64 architecture. Lambda Web Adapter also offered arm64 images in 2024, so an arm64 deployment is valid when the container image and Lambda function are both configured for arm64.

About the author

Takahiro Iwasa

Takahiro Iwasa

Software Developer

This blog shares technical notes from hands-on projects—architecture, implementation, and AWS service integrations.