Blog

Web Applications on AWS Lambda Containers

In this article, we will explore ways to package and run an example web application in a container on AWS Lambda.

Short History

On December 1, 2020, AWS launched container support for AWS Lambda. Since then, many people now prefer packaging their lambda functions in the container format as opposed to the zip file format. There are several advantages to packaging your lambda as a container image. First, you get more space since the image can be up to 10 GB. Next, we can make the image portable using a lambda extension.

In this post, we will explore an example solution and packaging it as a lambda container image that will be portable and work on any container platform that AWS offers. We will also cover the aws-lambda-web-adapater, which eliminates the need to write handler functions. We can simply run a webserver on port 8080 and the adapter will automatically handle the incoming request and forward the request as an http request to the webserver.

Example Dockerfile for AWS Lambda

We start off with the AWS Lambda nodejs image from ECR Public. This image comes with the Node.js runtime and AWS Lambda Runtime Interface Client preinstalled. There are several other base images for applications written in Python, Ruby, Java, Go, C#, and Rust.

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

Then we add a build stage.

FROM base AS build

WORKDIR /app

COPY ./*.json ./

RUN --mount=type=cache,target=/root/.npm \
    npm ci --progress=false

COPY ./src/ ./src/

RUN \
    npm run build

Then we create a runtime image. In this stage, we will install aws-lambda-web-adapter from the image on ECR Public.

FROM base AS runtime

ARG PORT=8080

ENV PORT=${PORT} \
    NODE_ENV=production \
    AWS_LWA_ENABLE_COMPRESSION=true

COPY --from=build /app/package*.json ${LAMBDA_TASK_ROOT}

RUN --mount=type=cache,target=/root/.npm \
    npm ci --audit=false --progress=false

COPY --from=build /app/dist/ ${LAMBDA_TASK_ROOT}/dist/

# Install the lambda-web-adapter
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter

EXPOSE ${PORT}

ENTRYPOINT ["node", "dist/main"]

The aws-lambda-web-adapter is an extension from AWS. Extensions are binaries that are executed during the startup phase of the lambda function.

Web application

Any web application that listens on http port 8080 (unless the PORT environment variable is set otherwise) can be used. Note that some extensions may work on reserved ports such as port 3000 for the insights extension, therefore running on port 8080 is recommended.

Consider a minimal TypeScript web application:

// src/main.ts
import { createServer } from 'node:http';
const hostname = '0.0.0.0';
const port = +(process.env.PORT || 8080);
const server = createServer((_, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

This will create a basic http server listening on all addresses over port 8080 by default. A simple docker build . --tag lambda && docker run --rm -p 8080:8080 lambda will allow you to run the application.

A basic compose file will also allow you to run the web application locally:

# compose.yml
services:
  lambda:
    build:
      context: .
    ports:
      - '8080:8080'

With the compose file, you can run docker compose build && docker compose up

aws-lambda-web-adapter

The aws-lambda-web-adapter is an extension to run web applications on AWS Lambda. The adapter supports the following services:

  • Amazon API Gateway Rest API and Http API endpoints - if you need advanced features such as validation, authorizers, and OpenAPI spec-based configuration
  • Lambda Function URLs - if you just need to expose a HTTPS url to the public
  • Application Load Balancer - if you have an existing application running on ECS or EKS or don't need of the features of API Gateway

Diagram

These services offer multiple ways of exposing a web application running on a container-based lambda with the aws-lambda-web-adapter extension installed.

Deployment

Once the container image is built, it must be tagged and pushed to an ECR repository. After the image is pushed, we can create the Lambda function, specifying the ECR repository and tag where the image is stored. The function must then be exposed via API Gateway, a Function URL, or an ALB. This can be done via the AWS Console, the AWS CLI, or through infrastructure as code (IaC) such as Terraform.

Lambda Execution

The way the container is executed on lambda is slightly different than it is when you run it locally. In the Lambda environment, AWS environment variables are injected for configuration. The runtime interface client (RIC) from the lambda base image is started and sends an HTTP GET request to the Lambda runtime API to get the next invocation event. Normally, a handler function must be written to handle the event (in this case for Node.js). However, the aws-lambda-web-adapter extension will automatically intercept the events from the RIC, and will translate the events back into standard HTTP requests for the web application to handle. Lambda extensions are placed in the /opt/extensions directory and are executed during startup before any events are handled, which is shown in the lambda execution logs. For a description of the cold start and invocation, see Execution Environments. AWS Lambda for the containers developer goes into depth about how containers run on Lambda.

Conclusion

In this article, we explored how to package and run a web application as a container on AWS Lambda. By using the aws-lambda-web-adapter, we've simplified the process of running HTTP-based applications on Lambda. Whether you're using API Gateway, Lambda Function URLs, or an Application Load Balancer, these methods provide flexibility and power in deploying serverless web applications with container images.

A complete example is available on GitHub

git clone https://github.com/rearc/blog-aws-lambda-containers.git
cd blog-aws-lambda-containers/
docker compose build && docker compose up
Next steps

Ready to talk about your next project?

1

Tell us more about your custom needs.

2

We’ll get back to you, really fast

3

Kick-off meeting

Let's Talk