Rearc Core Values
About our company values and how we got here.
In this article, we will explore ways to package and run an example web application in a container on AWS Lambda.
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.
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.
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
The aws-lambda-web-adapter is an extension to run web applications on AWS Lambda. The adapter supports the following services:
These services offer multiple ways of exposing a web application running on a container-based lambda with the aws-lambda-web-adapter extension installed.
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.
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.
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
Read more about the latest and greatest work Rearc has been up to.
About our company values and how we got here.
Avoiding fine-grained permissions in GCS, employing intelligent backup and snapshot management, adapting to changing data-usage patterns, and more.
How to run web applications on AWS Lambda using container images and the web adapter
How to streamline a deployment pipeline being bogged down by container bloat
Tell us more about your custom needs.
We’ll get back to you, really fast
Kick-off meeting