Building AWS Lambda Container Images

AWS Lambda is an event-driven, serverless compute service that enables teams to execute code for any back-end service without having to deploy or configure their own servers. As one of its most essential features, Lambda reduces costs for running interactive back-ends and processing data at scale.

Lambda has become increasingly popular in recent years due to its ability to simplify the development and deployment of serverless applications. Some reasons for its popularity include:

  • Cost effectiveness: With Lambda, you only pay for the time your code is actually running, which makes it a cost-effective option for building and scaling applications.
  • Scalability: Lambda is designed to scale automatically to handle the number of requests or events it receives, which makes it a great choice for building event-driven applications or microservices.
  • Flexibility: Lambda functions can be written in a variety of programming languages, and can be triggered by a wide range of events, which makes them flexible and easy to integrate with other AWS services.
  • Community support: The Lambda community is constantly growing, with developers sharing best practices, code examples, and tutorials, which makes it easier for new developers to get started with the service.

What is and Why Lambda Image?

A Lambda image is a custom runtime image that includes a pre-built version of the operating system and other dependencies needed to run the Lambda function.

When creating a Lambda function, AWS supports the use of a container image as the deployment package. With this functionality, you can use the Lambda API or the Lambda console to create a function that is defined by the container image. Once the image is deployed, the underlying code can be further updated and tested to configure various Lambda functions.

Using a Lambda image can offer several benefits, such as:

  • Faster startup times: When you use a Lambda image, the runtime environment is already pre-built and cached, which can result in faster startup times and reduce the latency of your function.
  • Customisation: Ability to include additional dependencies and customisations that are specific to your use case, which can help you streamline your code and improve performance.
  • Consistency: You can ensure the runtime environment for your function is consistent across all deployments, which can help you avoid compatibility issues and make it easier to manage and troubleshoot your code.
  • Security: Ensure your dependencies are up-to-date and free from known vulnerabilities, which can help you improve the security of your code.

Deploy Lambda using image

There are multiple approaches to deploying Lambda functions using a container image. However, for the purpose of this blog, we will use an AWS base image to deploy Lambda functions using CLI commands.

Prerequisites

  1. Docker Desktop for Docker CLI commands
  2. AWS CLI for AWS ECR (Elastic Container Registry) API calls
  3. Lambda function code in any of the supported language (in this article we will use node) 
    • Use LAMBDA_TASK_ROOT in dockerfile to copy the code for deployment

Deployment Steps

Step 1: Build and Test the Lambda Image Locally

Create a new folder and execute the below commands:

Initialise the node project.

# this will initiate a node project inside new folder and create basic package.json file
npm init -y

Create app.js and copy the below code. This is a basic function exported to return a json object.

const verboseLog = (logMessage) => {
if (process.env.LOG_LEVEL === logLevels.verbose) console.log(logMessage)
}
const normalLog = (logMessage) => {
console.log(logMessage)
}

exports.handler = async (event) => {
normalLog(`event: ${JSON.stringify(event)}`)
const successMessage = ‘hello world’
const response = {
  statusCode: 200,
  body: successMessage
}
return response
}

Create a dockerfile.

FROM public.ecr.aws/lambda/nodejs:18

COPY package.json app.js ${LAMBDA_TASK_ROOT}
RUN npm install

CMD [ “app.handler” ]

Execute the below commands to build a docker image locally and test if it works as expected.

# build docker image
docker build -t lambda-container-demo:0.0.1 .

# Use this image to run a container locally and expose container port 8080 on host port 9090

docker rm -f lambda-container && \
docker run -d -p 9000:8080 \
–name lambda-container \
lambda-container-demo:0.0.1

# To test it locally
curl -XPOST “http://localhost:9000/2015-03-31/functions/function/invocations” -d ‘{}’
Based on the code in the Lambda function, you should get below output.

Step 2: Publish Image to AWS ECR

In this step, we will publish the previously created image to AWS ECR. As a prerequisite, ensure you have valid AWS credentials.

Execute the below commands to publish the image.

replace <region> and <account number> with your applicable values
# login to ecr
aws ecr get-login-password \
  | docker login \
  –username AWS \
  –password-stdin \
  https://<account number>.dkr.ecr.<region>.amazonaws.com

# lets first create a new repository for our testing purposes, however, if you want to use an existing repository feel free to skip this step
aws ecr create-repository \
  –repository-name ${REPOSITORY_NAME} \
  –image-scanning-configuration scanOnPush=true \
  –image-tag-mutability MUTABLE \
  –no-cli-pager

This code will create a new repository. This can be validated by going to Elastic Container Registry services on AWS console, and clicking on Repositories on the left pane.

Next let’s create a tag locally first, and then publish image to ECR.

# create new tag
docker tag lambda-container-demo:0.0.1 <account number>.dkr.ecr.<region>.amazonaws.com/lambda-container-demo:0.0.1
# create latest tag
docker tag lambda-container-demo:0.0.1 <account number>.dkr.ecr.<region>.amazonaws.com/lambda-container-demo:latest

# push both tags to ecr
docker push <account number>.dkr.ecr.<region>.amazonaws.com/lambda-container-demo:0.0.1

docker push <account number>.dkr.ecr.<region>.amazonaws.com/lambda-container-demo:latest

This is what the push command output looks like.

Validate on the AWS console if the image has been pushed successfully to the repository.

Step 3: Makefile

Here is the makefile with all the required targets. Update REGION and ACCOUNT_NUMBER and execute make all. It should create an ECR repository and publish a docker image with “0.0.1” and “latest” tags.

 

REGION=ap-southeast-2
ACCOUNT_NUMBER=<account_number>e.g.1234567890
TAG=0.0.1
IMAGE_NAME=lambda-container-demo2
CONTAINER_NAME=lambda-container2
REPOSITORY_NAME=lambda-container-demo2

.PHONY: build
build:
  docker build -t ${IMAGE_NAME}:${TAG} .

.PHONY: run
run:
  docker rm -f lambda-container && \
  docker run -d -p 9000:8080 –name ${CONTAINER_NAME} ${IMAGE_NAME}:${TAG}

.PHONY: test
test:
  curl -XPOST “http://localhost:9000/2015-03-31/functions/function/invocations” -d ‘{}’

.PHONY: login_ecr
login_ecr:
  aws ecr get-login-password \
  | docker login \
  –username AWS \
  –password-stdin \
  https://${ACCOUNT_NUMBER}.dkr.ecr.${REGION}.amazonaws.com
 
.PHONY: create_repository
create_repository:
  aws ecr create-repository \
  –repository-name ${REPOSITORY_NAME} \
  –image-scanning-configuration scanOnPush=true \
  –image-tag-mutability MUTABLE \
  –no-cli-pager

.PHONY: create_image_tags
create_image_tags:
  docker tag ${IMAGE_NAME}:${TAG} \
  ${ACCOUNT_NUMBER}.dkr.ecr.${REGION}.amazonaws.com/${IMAGE_NAME}:${TAG} && \
  docker tag ${IMAGE_NAME}:${TAG} \
  ${ACCOUNT_NUMBER}.dkr.ecr.${REGION}.amazonaws.com/${IMAGE_NAME}:latest

.PHONY: docker_push
docker_push:
  docker push ${ACCOUNT_NUMBER}.dkr.ecr.${REGION}.amazonaws.com/${IMAGE_NAME}:${TAG} \
  && \
  docker push ${ACCOUNT_NUMBER}.dkr.ecr.${REGION}.amazonaws.com/${IMAGE_NAME}:latest

all: build run login_ecr create_repository create_image_tags docker_push

Create Lambda Function

We have two options:

  • Using AWS CLI
  • Using AWS Console
Using AWS CLI

 

# Create new lambda function
aws lambda createfunction \
  —functionname test1 \
  —packagetype Image \
  —code ImageUri=${ACCOUNT_NUMBER}.dkr.ecr.${REGION}.amazonaws.com/lambda-container-demo:0.0.1 \
  –role arn:aws:iam::${ACCOUNT_NUMBER}:role/service-role/${lambda_role_name}

# Invoke lambda to test output
aws lambda invoke \
  —functionname test1 \
  response.json

# output of above command
{
    “StatusCode”: 200,
    “ExecutedVersion”: “$LATEST”
}

Using AWS Console
  • Go to Lambda service on the AWS console, and click the ‘Create function’ button. 
  • Select ‘Container image’ and click on the ‘Browse images’ button.  
  • Select your repository from the dropdown and select the image.
  • Once done, click on the ‘Create function’ button at the bottom and your Lambda function will be created successfully. 
  • Now, let’s test it by creating a new test event. And yes, Lambda responds with the expected output.

The use of base images in containers is one of the several Lambda function deployment techniques supported by AWS. It is simple for development teams to construct scalable serverless workloads with different dependencies using these Amazon Lambda images.

In this post, I demonstrated how to build a Docker image and deploy it in the Lambda service using AWS Elastic Container Registry. This was a very simple use case that you can use as a basis to build your own complex Lambda functions.

Enjoyed this blog?

Share it with your network!

Move faster with confidence