home

Blog


Host your async app without spending a dime using AWS Lambda


If you have an async app, an internal tool, side project or PoC, there's no reason to pay for a machine that's awake all day for a job that takes so little time. That's where AWS Lambda comes in, charging you only when your function gets executed, with a generous free tier. So how do you take what you have, written with FastAPI, Express, Django, Fastify or whatever, and drop it into Lambda without a lot of refactoring?

AWS Lambda Web Adapter

AWS maintains an open source project called the Lambda Web Adapter which is a small proxy that sits in front of your app. Lambda invokes the adapter, the adapter translates the event into a plain HTTP request, forwards it to your app and translates the response back into whatever shape Lambda expects. Your app has no idea it's running inside Lambda. It just sees an HTTP request on a port, like it always did.

This means you don't have to refactor anything (not this time at least), you just add the adapter (copying the binary into your image if you're using a container or adding a Lambda Layer) and point it to your app's port. Here's a minimal Dockerfile example for a NodeJS app:

FROM public.ecr.aws/lambda/nodejs:24 AS base

# This is required to make the adapter work as expected
ENTRYPOINT []

# Copy the Lambda Web Adapter binary from its distributed image
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:1.0.1 /lambda-adapter /opt/extensions/lambda-adapter

# Tells the adapter which port to listen
ENV AWS_LWA_PORT=3000

COPY package.json ${LAMBDA_TASK_ROOT}/package.json
COPY package-lock.json ${LAMBDA_TASK_ROOT}/package-lock.json

RUN npm ci --production

COPY . .

CMD ["node", "app.js"]

The adapter handles that translation transparently, including support for streaming responses if your app relies on them. For instance, this very website is running inside a Lambda function (yup).

Packaging your app

I've given an example using Docker images, but there are a few other options for you to package your app, and they all fit different needs.

ZIP

The simplest option. You zip your code and dependencies, upload it directly (through the console or CLI), and Lambda runs it on its own managed runtime. There's no image to build or registry to manage, but you're capped at 250 MB unzipped (50 MB zipped), and native dependencies need to match Lambda's underlying OS.

S3

Practically the same as the ZIP approach, but it can accept packages greater than 50 MB zipped. You push the zip to a bucket and point Lambda at that object instead of uploading it inline. Same 250 MB limit applies.

Docker

The option to reach for once you outgrow the other limits, need OS-level dependencies that aren't in the standard runtimes, or just want your Lambda environment to look like everything else you already run in containers. Also, the images can go up to 10 GB.

Once the application is packaged and the Lambda function is deployed, you need something in front of it that speaks HTTPS.

Getting an endpoint: Lambda Function URL

Function URLs are the fastest option. Every Lambda function can have a dedicated HTTPS endpoint enabled with a single setting, no extra resources to create. You get a generated URL and requests are forwarded straight to your function. If you just need an endpoint for a webhook, an internal service, or a quick prototype, this is usually all you need.

Getting an endpoint: AWS HTTP API Gateway

HTTP API Gateway makes sense once you need more than a bare endpoint: custom domains, request throttling, built-in authorizers, etc. It's a small extra piece of infrastructure to manage, but it's also cheap and pay-per-request, so it doesn't break the "free unless you're actually getting traffic" (or a DDoS attack lol) model.

Worth knowing before you commit

  • Lambda has a hard execution limit, so this isn't a fit for long-lived connections;
  • Cold starts are real, especially with container images, but they shouldn't be such a problem in a function with more frequent traffic. So, keep the image lean and consider using provisioned concurrency if it matters. However, this is basically spinning up a server and paying for the time it's up;
  • A single Lambda function reserved concurrency serves a single request, so plan your concurrency accordingly, to avoid being surprised by throttling;

Wrap-up

For various async apps out there, some idle, others with spiky traffic or without persistent connections, this setup gets you a real production app with an endpoint to receive requests that'll cost you nothing until someone actually uses it! So, what are you going to build with it?!

Powered by Simple Blog API