Skip to main content

AWS Lambda Function with Docker Container: Run containerized Python Code via AWS CLI

1195 words·
AWS AWS CLI Lambda Elastic Container Registry CloudWatch Docker Python
Table of Contents

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