DEV Community

Cover image for Deploy FastAPI to AWS in 60 Seconds
Eric D Johnson for AWS

Posted on • Originally published at edjgeek.com

Deploy FastAPI to AWS in 60 Seconds

Deploy a standard FastAPI app to AWS Lambda serverlessly in two commands. No Docker. No handler code. No code changes.

How do I deploy FastAPI to AWS Lambda without code changes?

You add Lambda Web Adapter as a Lambda Layer, and your FastAPI app deploys to AWS Lambda with sam build && sam deploy. The same code you run locally with uvicorn goes straight to production without any modifications. No handler wrapper, no Mangum, no Dockerfile.

Lambda scales to zero, so you pay nothing when idle, and your app never knows it's running on Lambda. In this post, I walk through how to set this up from scratch, explain the architecture, and deploy a working API in about 60 seconds of actual commands.

What is Lambda Web Adapter and how does it work with FastAPI?

If you've ever deployed a FastAPI app to Lambda the traditional way, you know the drill: install Mangum, wrap your app in a handler function, build a Docker image, push to ECR, configure API Gateway. It works, but now your app has Lambda-specific code baked in.

Lambda Web Adapter takes a completely different approach. It's an open-source Lambda Layer maintained by AWS. You add it to a function, and it handles all the translation between Lambda's event format and plain HTTP. When a request comes in, the adapter intercepts the Lambda invocation and forwards it as a normal HTTP request to a local web server. In this case, uvicorn running your FastAPI app on port 8080.

The flow looks like this:

Request flow

Your app receives normal HTTP requests and returns normal HTTP responses. It has no idea it's running inside a Lambda function. This means the same FastAPI app runs on Lambda, in a Docker container on ECS, or on your laptop with uvicorn. Zero changes between environments.

With that in mind, let's look at what the actual code looks like.

Can I use my existing FastAPI app on Lambda without changes?

Yes. And that's the whole point. Here's the complete application. Take a look and notice what's not there: no Lambda imports, no handler function, no Mangum wrapper. This is a standard FastAPI app you could run anywhere.

main.py

import asyncio
from typing import Optional

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(title="Items API")

_items: dict[int, dict] = {}
_next_id = 1


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float


class ItemResponse(Item):
    id: int


@app.get("/health")
def health():
    return {"status": "ok"}


@app.get("/items", response_model=list[ItemResponse])
def list_items():
    return [{"id": k, **v} for k, v in _items.items()]


@app.post("/items", response_model=ItemResponse, status_code=201)
def create_item(item: Item):
    global _next_id
    item_id = _next_id
    _next_id += 1
    _items[item_id] = item.model_dump()
    return {"id": item_id, **_items[item_id]}


@app.get("/items/{item_id}", response_model=ItemResponse)
def get_item(item_id: int):
    if item_id not in _items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"id": item_id, **_items[item_id]}


@app.delete("/items/{item_id}", status_code=204)
def delete_item(item_id: int):
    if item_id not in _items:
        raise HTTPException(status_code=404, detail="Item not found")
    del _items[item_id]


@app.get("/async-demo")
async def async_demo():
    await asyncio.sleep(1)
    return {"message": "done", "waited_seconds": 1}
Enter fullscreen mode Exit fullscreen mode

A CRUD API with an async endpoint. Nothing special. That's the point.

The only other piece is run.sh, a tiny shell script that starts uvicorn. This is the entrypoint Lambda will call:

#!/bin/bash
export PYTHONPATH=/var/task:$PYTHONPATH
exec python -m uvicorn main:app --host 0.0.0.0 --port 8080
Enter fullscreen mode Exit fullscreen mode

And requirements.txt with three dependencies:

fastapi
uvicorn[standard]
pydantic
Enter fullscreen mode Exit fullscreen mode

That's the entire application. You can run it locally right now with uvicorn main:app --reload --port 8080 and get the same behavior you'll get on Lambda. No adapter, no layer, no SAM. Locally, it's a normal FastAPI app.

So where does the Lambda configuration actually go? That brings us to the one file that makes the deployment work.

What does the SAM template look like?

All the Lambda-specific configuration lives in a single file, and it's not your application code. It's the AWS SAM template. SAM (Serverless Application Model) is an open-source framework that extends CloudFormation to make serverless deployments simpler. Here's the complete template:

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: FastAPI on AWS Lambda using Lambda Web Adapter (zip, no Docker)

Resources:
  FastApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: app/
      Handler: run.sh
      Runtime: python3.12
      Architectures:
        - arm64
      MemorySize: 512
      Timeout: 30
      Layers:
        - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerArm64:24
      Environment:
        Variables:
          AWS_LWA_PORT: '8080'
          AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
      Events:
        Api:
          Type: HttpApi
      Policies:
        - AWSLambdaBasicExecutionRole

Outputs:
  ApiUrl:
    Description: API Gateway endpoint URL
    Value: !Sub https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

Let's take a look at the important parts:

  • Handler: run.sh means the entrypoint is a shell script that starts uvicorn, not a Python handler function. That's what makes this work.
  • Layers is the Lambda Web Adapter layer ARN. This is the arm64 version (layer 24, v0.8.4). The layer provides the /opt/bootstrap wrapper that intercepts invocations and proxies them to your server.
  • AWS_LWA_PORT: '8080' tells the adapter which port your app listens on.
  • AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap tells Lambda to use the adapter's bootstrap wrapper instead of invoking your handler directly.
  • Architectures: arm64 runs on Graviton2, AWS's Arm-based processor. Better price-performance than x86. No code changes needed since Python is architecture-independent.
  • Events: HttpApi creates an Amazon API Gateway HTTP API (v2). This one line gives you a lot: a publicly accessible URL, automatic stage deployment, built-in CORS support, and request routing to your Lambda function. HTTP APIs are ~70% cheaper than REST APIs ($1.00 vs $3.50 per million requests) and have lower latency because they skip the request/response transformation layer. For a framework like FastAPI that handles its own routing, HTTP API is the right choice.

And that's it. The whole template is 30 lines. Your app code has zero lines of Lambda-specific anything.

Now that the code and configuration are in place, let's deploy it.

How do I deploy FastAPI to Lambda using SAM CLI?

Now for the fun part. You need AWS CLI, AWS SAM CLI, and Python 3.12.

No Docker required. That's unusual for Lambda deployments with custom dependencies, but Lambda Web Adapter works as a zip deployment with a layer. SAM handles the packaging.

First deployment (sets up your stack name and region):

sam build && sam deploy --guided
Enter fullscreen mode Exit fullscreen mode

SAM asks you a few questions: stack name, region, whether to allow IAM role creation. Answer them once, and it creates a samconfig.toml file so subsequent deploys need no prompts.

Every deployment after that:

sam build && sam deploy
Enter fullscreen mode Exit fullscreen mode

Two commands. That's the "60 seconds" in the title. The API URL is printed at the end of the deploy output:

Outputs
---------------------------------------------------------------------------
Key                 ApiUrl
Description         API Gateway endpoint URL
Value               https://abc123xyz.execute-api.us-east-1.amazonaws.com
---------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

The URL format is https://<api-id>.execute-api.<region>.amazonaws.com. Grab it and you're ready to test.

Teardown

When you're done experimenting:

sam delete
Enter fullscreen mode Exit fullscreen mode

Removes everything: the Lambda function, the API Gateway, the IAM role. Clean slate, no lingering costs.

How do I test and run FastAPI locally?

Once you have the deployed URL, try it out:

BASE_URL=https://<api-id>.execute-api.<region>.amazonaws.com

# Health check
curl $BASE_URL/health

# List items (empty)
curl $BASE_URL/items

# Create an item
curl -X POST $BASE_URL/items \
  -H "Content-Type: application/json" \
  -d '{"name": "Widget", "description": "A fine widget", "price": 9.99}'

# Get item by ID
curl $BASE_URL/items/1

# Delete item
curl $BASE_URL/items/1 -X DELETE

# Async endpoint - demonstrates non-blocking I/O
curl $BASE_URL/async-demo
Enter fullscreen mode Exit fullscreen mode

And here's a nice bonus: FastAPI's interactive docs work too. Open $BASE_URL/docs in a browser and you get the full Swagger UI, served from Lambda. No extra configuration needed.

Local development

But here's the thing about this setup: you don't need Lambda running to develop. The local workflow is identical to any other FastAPI project:

cd app
pip install -r requirements.txt
uvicorn main:app --reload --port 8080
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:8080/docs for the interactive API docs. Make changes, uvicorn reloads, test instantly. When you're happy, sam build && sam deploy.

No separate "local Lambda emulator" step. No SAM local invoke. No Docker Compose file for local testing. The app is the app, everywhere.

Lambda Web Adapter vs Mangum: which should you use for FastAPI?

Now, I understand what you're thinking: "What about Mangum?" It's a solid project, and for a long time it was the only practical way to run FastAPI on Lambda. It translates API Gateway events into ASGI calls so frameworks like FastAPI can process them. But it comes with trade-offs worth understanding:

Lambda Web Adapter Mangum
App code changes None Add handler + wrap app
Local dev parity Identical (same uvicorn command) Need separate local entry point
Framework coupling Zero. Works with any HTTP framework ASGI-only
Docker required No (zip + layer) Usually yes (for dependencies)
Additional cold start +100-200ms (uvicorn startup) +10-20ms (thin wrapper, no server process)
Language lock-in None. Works with Python, Node, Go, Rust, Java... Python only
Maintenance AWS-maintained layer Community-maintained

The cold start difference is real but small. For most APIs, an extra 100-200ms on cold start is a worthy trade-off for keeping your app completely portable. The same FastAPI code runs on Lambda, ECS, a VM, or your laptop with zero changes.

The bottom line: With Mangum, your app knows it's on Lambda. With Lambda Web Adapter, it doesn't. If portability and local dev parity matter to you, Lambda Web Adapter is the better choice. If you need the absolute lowest cold start and don't care about portability, Mangum still works fine.

How much does it cost to run FastAPI on Lambda?

One of the most common questions I hear: "What will this cost me?" With Lambda, the answer depends entirely on traffic. If nobody calls your API, you pay nothing. Literally zero.

For a typical low-traffic API (100,000 requests/month, 200ms average duration, 512MB memory):

Component Monthly cost
Lambda compute ~$0.21
API Gateway (HTTP API) ~$0.10
Total ~$0.31/month

Compare that to a t3.micro EC2 instance running 24/7: ~$7.60/month even when nobody is calling it. Or an always-on ECS Fargate task: ~$15-30/month depending on configuration.

The Lambda free tier covers 1 million requests and 400,000 GB-seconds per month, and it's always free (not time-limited). The HTTP API (API Gateway v2) free tier adds another 1 million requests/month for the first 12 months. Between the two, most side projects and early-stage APIs cost effectively zero. You'll start paying meaningful amounts when you cross roughly 5-10 million requests per month.

What are the cold start times for FastAPI with Lambda Web Adapter?

Cold starts are the single most common concern people raise about running web frameworks on Lambda. I covered this topic in depth in Cold Starts Are Dead, and the short version is: in 2026, they're a fraction of what they used to be. But let's be specific about what this setup actually adds.

The extra cold start overhead from Lambda Web Adapter is ~100-200ms. That's the time uvicorn needs to start up inside the Lambda execution environment. The adapter itself initializes in single-digit milliseconds.

In practice, a cold start for this setup looks roughly like this (based on the Lambda Web Adapter maintainer's estimates and general Python 3.12 runtime observations, not formal benchmarks):

Phase Duration
Lambda init (runtime + dependencies) ~300-500ms
Lambda Web Adapter + uvicorn startup ~100-200ms
Total cold start ~400-700ms

After the first request, subsequent invocations are warm and respond in single-digit milliseconds. Lambda keeps the execution environment alive for several minutes between requests, so moderate traffic rarely sees cold starts. For an API handling steady traffic throughout the day, cold starts affect maybe 1-2% of requests.

If cold starts matter for your use case, you have options. Enable Lambda SnapStart (Python support launched in 2024) to snapshot the initialized environment. Or use provisioned concurrency to keep instances warm. Both add cost but eliminate cold starts entirely.

What are the next steps after deploying FastAPI to Lambda?

The full source code is on GitHub. Clone it, deploy it, break it. Make it yours.

Once you have the basic setup working, here are some natural next steps:

  • Custom domain: Add a custom domain name via API Gateway custom domain mappings so your API lives at api.yourdomain.com instead of the generated URL.
  • CI/CD pipeline: Set up AWS SAM Pipelines or a GitHub Action to deploy on every push to main.
  • Database: Replace the in-memory dict with DynamoDB for persistent storage.
  • Authentication: Add a Lambda authorizer or use API Gateway's built-in JWT authorizer.
  • Monitoring: Enable AWS X-Ray tracing and Amazon CloudWatch alarms.

Lambda Web Adapter works with any HTTP framework in any language. FastAPI today, Flask tomorrow, Express next week. The pattern is the same: write a standard web app, add the layer, deploy with SAM.

The serverless tax of rewriting your app for Lambda is gone. Your framework code stays framework code.

Top comments (0)