AWS Lambda Docker Overview #
-
Lambda has maximal execution time limit of 15 minutes
-
Image size limit: The container image can be up to 10 GB in size
-
The image must be stored in AWS Elastic Container Registry (ECR)
AWS Elastic Container Registry (ECR) #
Create ECR Repository #
# Create a repository
aws ecr create-repository \
--repository-name lambda-docker-example \
--region eu-central-1
# Shell output:
{
"repository": {
"repositoryArn": "arn:aws:ecr:eu-central-1:012345678912:repository/lambda-docker-example",
"registryId": "012345678912",
"repositoryName": "lambda-docker-example",
"repositoryUri": "012345678912.dkr.ecr.eu-central-1.amazonaws.com/lambda-docker-example",
"createdAt": "2025-02-05T09:56:49.433000+00:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
Docker Login #
# Retrieve an authentication token and authenticate the Docker client
aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 012345678912.dkr.ecr.eu-central-1.amazonaws.com/lambda-docker-example
# Shell output:
WARNING! Your password will be stored unencrypted in /home/ubuntu/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
Login Succeeded
Python Docker Container #
Project Folder #
# Create project folder
TF_PROJECT_NAME=aws-lambda-email-validator
mkdir $TF_PROJECT_NAME && cd $TF_PROJECT_NAME
The file and folder structure looks like this:
aws-lambda-email-validator
├── app.py
├── Dockerfile
Dockerfile #
- Dockerfile
# Lambda base image for Docker
FROM public.ecr.aws/lambda/python:latest
# Copy the application code into the container
COPY app.py .
# Set the Lambda handler (should match function name in app.py)
CMD ["app.validate"]
Python App #
- app.py
import json
import re
def validate(event, context):
event_body = json.loads(event['body'])
email_regex = re.compile('^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
matches = email_regex.match(event_body['email']) != None
response = {
'statusCode': 200,
'body': json.dumps({ 'result': matches })
}
return response
Build Docker Image #
# Build container image
docker build -t lambda-example .
Run / Test Image Locally #
Run Container #
# Test the image locally with Docker
docker run -d -p 9000:8080 lambda-example
# Verify the container
docker ps
# Shell output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1709cb589661 lambda-example "/lambda-entrypoint.…" 1 second ago Up 1 second 0.0.0.0:9000->8080/tcp, [::]:9000->8080/tcp quizzical_leavitt
Test Python App #
# Test with example email address: "test@example.com"
curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"body": "{\"email\": \"test@example.com\"}"}'
# Shell output:
{"statusCode": 200, "body": "{\"result\": true}"}
# Test with example email address: "test.example.com"
curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"body": "{\"email\": \"test.example.com\"}"}'
# Shell output:
{"statusCode": 200, "body": "{\"result\": false}"}
# Stop and remove the container
docker stop 1709cb589661 && docker rm 1709cb589661
Tag the Image for AWS ECR #
Docker images need a fully qualified name to push
# Tag the local Docker image to associate it with the ECR repository
docker tag lambda-example:latest 012345678912.dkr.ecr.eu-central-1.amazonaws.com/lambda-docker-example:latest
Verify the image:
# List Docker images
docker images
# Shell output:
REPOSITORY TAG IMAGE ID CREATED SIZE
012345678912.dkr.ecr.eu-central-1.amazonaws.com/lambda-docker-example latest 782b3965a1a2 4 minutes ago 515MB
lambda-example latest 782b3965a1a2 4 minutes ago 515MB
Push the Image to ECR #
# Push the image to ECR
docker push 012345678912.dkr.ecr.eu-central-1.amazonaws.com/lambda-docker-example:latest
# Shell output:
The push refers to repository [012345678912.dkr.ecr.eu-central-1.amazonaws.com/lambda-docker-example]
fdcd5b75a23e: Pushed
d40d1b6fe31d: Pushed
2195ebbc1042: Pushed
0091b0f618e4: Pushed
4225886892d5: Pushed
393348208dca: Pushed
6281cd02ff74: Pushed
latest: digest: sha256:7f66aedb1bf05d12866d4c1cf81f6605542cd56832476019598ce8c3d9b59c62 size: 1785
AWS Lambda Function #
Create a Role for Lambda #
# Create an IAM Role for Lambda
aws iam create-role --role-name jkw-lambda-example \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}'
# Shell output:
{
"Role": {
"Path": "/",
"RoleName": "jkw-lambda-example",
"RoleId": "AROARCHUALINRAVD34AFF",
"Arn": "arn:aws:iam::012345678912:role/jkw-lambda-example",
"CreateDate": "2025-02-05T10:35:53+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
Attach the “AWSLambdaBasicExecutionRole” managed policy to the new role:
# Atach "AWSLambdaBasicExecutionRole" policy
aws iam attach-role-policy --role-name jkw-lambda-example\
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Verify the Role
aws iam list-roles | grep "jkw-lambda-example"
# Shell output:
"RoleName": "jkw-lambda-example",
"Arn": "arn:aws:iam::012345678912:role/jkw-lambda-example",
Create Lambda Function #
-
The Lambda function must be in the same AWS region as the ECR container registry
-
Define the previously created IAM role ARN
# Create AWS Lambda function
aws lambda create-function --function-name lambda-email-validator \
--package-type Image \
--code ImageUri=012345678912.dkr.ecr.eu-central-1.amazonaws.com/lambda-docker-example:latest \
--role arn:aws:iam::012345678912:role/jkw-lambda-example \
--region eu-central-1
# Shell output:
{
"FunctionName": "lambda-email-validator",
"FunctionArn": "arn:aws:lambda:eu-central-1:012345678912:function:lambda-email-validator",
"Role": "arn:aws:iam::012345678912:role/jkw-lambda-example",
"CodeSize": 0,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2025-02-05T10:42:54.681+0000",
"CodeSha256": "7f66aedb1bf05d12866d4c1cf81f6605542cd56832476019598ce8c3d9b59c62",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "aa1a87d4-0563-4f74-941b-15296afcab12",
"State": "Pending",
"StateReason": "The function is being created.",
"StateReasonCode": "Creating",
"PackageType": "Image",
"Architectures": [
"x86_64"
],
"EphemeralStorage": {
"Size": 512
},
"SnapStart": {
"ApplyOn": "None",
"OptimizationStatus": "Off"
},
"LoggingConfig": {
"LogFormat": "Text",
"LogGroup": "/aws/lambda/lambda-email-validator"
}
}
Example Payload #
# Install jq for encoding
Install jq
# Create example payload files
echo '{"body": "{\"email\": \"test@example.com\"}"}' > payload1.json
echo '{"body": "{\"email\": \"test.example.com\"}"}' > payload2.json
# Encode payload files
cat payload1.json | base64 | tr -d '\n' > payload1_base64.json
cat payload2.json | base64 | tr -d '\n' > payload2_base64.json
Invoke Lambda Function #
# Invoke the Lambda function
aws lambda invoke --function-name lambda-email-validator \
--region eu-central-1 \
--payload file://payload1_base64.json \
response1.json
# Invoke the Lambda function
aws lambda invoke --function-name lambda-email-validator \
--region eu-central-1 \
--payload file://payload2_base64.json \
response2.json
# Shell output:
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
Verify the output:
# Cat the response files:
cat response1.json
cat response2.json
# Shell output:
{"statusCode": 200, "body": "{\"result\": true}"}
{"statusCode": 200, "body": "{\"result\": false}"}
List Lambda function Logs #
# List logs
aws logs tail /aws/lambda/lambda-email-validator --region eu-central-1
# Shell output:
28 MB Max Memory Used: 33 MB
2025-02-05T11:07:42.351000+00:00 2025/02/05/[$LATEST]8d1b3c1220844843b14f1d97f13a2b5c START RequestId: 769ee522-e88a-469d-b167-5df0afee8e12 Version: $LATEST
2025-02-05T11:07:42.352000+00:00 2025/02/05/[$LATEST]8d1b3c1220844843b14f1d97f13a2b5c END RequestId: 769ee522-e88a-469d-b167-5df0afee8e12
2025-02-05T11:07:42.353000+00:00 2025/02/05/[$LATEST]8d1b3c1220844843b14f1d97f13a2b5c REPORT RequestId: 769ee522-e88a-469d-b167-5df0afee8e12 Duration: 1.48 ms Billed Duration: 2 ms Memory Size: 1
...
CloudWatch Metrics #
List Available Metrics for Lambda #
# List
aws cloudwatch list-metrics --namespace "AWS/Lambda" --region eu-central-1 --no-cli-pager
# Shell output:
{
"Namespace": "AWS/Lambda",
"MetricName": "ConcurrentExecutions",
"Dimensions": []
},
{
"Namespace": "AWS/Lambda",
"MetricName": "Invocations",
"Dimensions": []
},
{
"Namespace": "AWS/Lambda",
"MetricName": "Duration",
"Dimensions": []
},
{
"Namespace": "AWS/Lambda",
"MetricName": "UnreservedConcurrentExecutions",
"Dimensions": []
},
{
"Namespace": "AWS/Lambda",
"MetricName": "Errors",
"Dimensions": []
},
{
"Namespace": "AWS/Lambda",
"MetricName": "Throttles",
"Dimensions": []
},
{
"Namespace": "AWS/Lambda",
"MetricName": "ClaimedAccountConcurrency",
"Dimensions": []
}
...
Lambda Function Invocation #
# List invocations
aws cloudwatch get-metric-statistics \
--namespace "AWS/Lambda" \
--metric-name "Invocations" \
--start-time "$(date -u -d '-30 minutes' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 60 \
--statistics Sum \
--region eu-central-1
# Shell output:
{
"Label": "Invocations",
"Datapoints": [
{
"Timestamp": "2025-02-05T11:03:00+00:00",
"Sum": 2.0,
"Unit": "Count"
},
{
"Timestamp": "2025-02-05T10:51:00+00:00",
"Sum": 1.0,
"Unit": "Count"
},
{
"Timestamp": "2025-02-05T11:10:00+00:00",
"Sum": 1.0,
"Unit": "Count"
},
{
"Timestamp": "2025-02-05T11:01:00+00:00",
"Sum": 1.0,
"Unit": "Count"
},
{
"Timestamp": "2025-02-05T11:07:00+00:00",
"Sum": 1.0,
"Unit": "Count"
},
{
"Timestamp": "2025-02-05T11:02:00+00:00",
"Sum": 1.0,
"Unit": "Count"
}
]
}
Lambda Function Duration #
# List
aws cloudwatch get-metric-statistics \
--namespace "AWS/Lambda" \
--metric-name "Duration" \
--start-time "$(date -u -d '-15 minutes' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 60 \
--statistics Average \
--region eu-central-1
# Shell output:
{
"Label": "Duration",
"Datapoints": [
{
"Timestamp": "2025-02-05T11:07:00+00:00",
"Average": 1.48,
"Unit": "Milliseconds"
},
{
"Timestamp": "2025-02-05T11:03:00+00:00",
"Average": 1.435,
"Unit": "Milliseconds"
},
{
"Timestamp": "2025-02-05T11:10:00+00:00",
"Average": 1.44,
"Unit": "Milliseconds"
}
]
}
Cleanup #
Delete Lambda Function #
# Delete the Lambda Function
aws lambda delete-function --function-name lambda-email-validator --region eu-central-1
Delete IAM Role #
# Detach the managed policy from the IAM Role
aws iam detach-role-policy --role-name jkw-lambda-example \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Delete the IAM Role
aws iam delete-role --role-name jkw-lambda-example
Delete ECR Repository #
# Delete the ECR Repository
aws ecr delete-repository --repository-name lambda-docker-example \
--region eu-central-1 --force