· Serverless · 3 min read
Deploying minimized serverless FastAPI to AWS Lambda.
With the addition of Lambda Web Adapters serverless WSGI/ASGI applications can be defined easily.
Serverless services have grown in popularity over the past years, with many offerings especially on the Javascript side. However, with the introduction of container image support to Lambda at the end of 2020, the possibilities opened up to deploying a wider variety of software to Lambda. With the addition of Lambda Web Adapters serverless WSGI/ASGI applications can be defined easily.
Writing your FastAPI backend
To deploy a FastAPI backend service, first we have to define our FastAPI backend. For this example, we will simply define a minimal FastAPI app with a hello world endpoint.
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
if __name__ == "__main__":
uvicorn.run(app, port=8000, log_level="info")
We make a slight difference to a regular FastAPI app, with the uvicorn.run
command in the __main__
of our application. This is due to use of PyInstaller, to compile the FastAPI application to an executable binary.
Containerizing our FastAPI application
After defining our FastAPI application and requirements.txt files, we can containerize and minify our application.
FROM python:3.12-slim as build
# Set the working directory in the container
WORKDIR /app
# Install any necessary dependencies
RUN apt-get update && apt-get install -y binutils patchelf && rm -rf /var/lib/apt/lists/*
# Copy the requirements file to the working directory
COPY ./app/ .
# Install the Python packages listed in requirements.txt
RUN pip install -r requirements.txt --no-cache-dir
# Install packages required for the packaging process
RUN pip install pyinstaller staticx
# Package the FastAPI application into an executable binary
RUN pyinstaller main.py --onefile --name fastapi_backend
# Package additional necessary dependencies for the executable binary using staticx
WORKDIR /app/dist
RUN staticx --strip fastapi_backend fastapi_backend_app
RUN mkdir tmp
FROM scratch as runtime
ENTRYPOINT ["/app/dist/fastapi_backend_app"]
# Copy the AWS Lambda Web Adapter binary to the container
COPY --chmod=0755 --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.3 /lambda-adapter /opt/extensions/lambda-adapter
# Copy the FastAPI application binary to the container
COPY --chmod=0755 --from=build /app/dist/fastapi_backend_app /app/dist/fastapi_backend_app
COPY --chmod=0755 --from=build /app/dist/tmp /tmp
ENV PORT=8000
And build by running:
docker build -t fastapi
Pushing the image to ECR
We can simply use the AWS and Docker CLIs to push our newly built image to ECR.
aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin {ECR_URI}
docker tag fastapi {ECR_URI}/{ECR_REPOSITORY}
docker push {ECR_URI}/{ECR_REPOSITORY}
Deploying to Lambda
After pushing our image to ECR, we can easily deploy it to Lambda using the AWS CLI.
aws lambda create-function --function-name {FUNCTION_NAME} --code ImageUri={ECR_URI}/{ECR_REPOSITORY}:latest --role {ROLE_ARN} --package-type Image
Benefits and drawbacks of minimization
Minimizing the size of our container image using PyInstaller and StaticX results in significantly smaller images when compared to installing our dependecies on Python slim image. Our container image is 26MB in size, which is about 1/5th of the size of a Python slim image with FastAPI installed.
The benefits of the smaller image size should be a faster cold start and less storage required on ECR. However, experiments have shown that the size of a container image on Lambda do not affect cold start times by much.
Drawbacks of minimizing our Lambda function include a limited flexibility of running our application and an increased volatility in the packacking process. Using PyInstaller and StaticX to compile our software to a binary is inherently more error prone, when compared to a more standardized method of installing packages. We are also constrained to running our application programmatically with Python, and cannot run the application with the Uvicorn CLI.
Getting the code
You can check the example code from Github.