top of page

Using Python and AWS Lambda to Resize Images

Updated: Oct 18, 2023


Python and AWS Lambda to resize images dynamically

Using Python and AWS Lambda to Resize Images


Image resizing is a common task in web development, especially when dealing with user-uploaded images. AWS Lambda provides a serverless computing service that allows you to run your code without managing any infrastructure.


In this blog post, we will explore how to use Python and AWS Lambda to resize images dynamically.

 

Table of Contents


Theory

There are primarily two ways to run Python code in AWS Lambda:

  1. Inline Code: In this method, you can write your Python code directly in the AWS Lambda console. This is suitable for simple or short-lived functions where the code logic is not extensive. You can quickly prototype and test your code without the need to package and upload any files.

  2. Function Package: In this method, you package your Python code, along with any dependencies or libraries, into a deployment package. The deployment package is then uploaded to AWS Lambda. This method is suitable for more complex functions that require external dependencies or have a larger codebase.


When using the Function Package approach, you have two options to package your Python code:

  • Zip Package: You can create a ZIP file containing your Python code and any required dependencies. The ZIP file is then uploaded to AWS Lambda using the AWS Management Console, AWS CLI, or an AWS SDK. AWS Lambda will extract and execute the code from the ZIP file.

  • Container Image: AWS Lambda also supports running Python code using container images. You can build a Docker container image that contains your Python code and dependencies, and then push the image to a container registry such as Amazon Elastic Container Registry (ECR). AWS Lambda will pull the container image and execute the code within the container.


Both methods have their advantages and considerations. Inline Code is more suitable for simple functions with minimal dependencies, while the Function Package approach offers more flexibility for complex applications.


It's essential to choose the method that aligns with your specific requirements and the complexity of your Python code. We will use the "Container Image" approach in this blog post.


The advantages of using the "Container Image" approach in AWS Lambda:

  • Flexible runtime environment customization for Python code

  • Simplified deployment of larger codebases or multiple files

  • Efficient dependency management within the container image

  • Reproducibility and consistent runtime environments across deployments

  • Enhanced portability for running Python code on different platforms

  • Seamless integration with container-related tools and services

  • Enables leveraging CI/CD pipelines, container orchestration platforms, and monitoring solutions

  • And the final but most important reason is that I love containers. I think they have made life very simple for us DevOps people.

Enough theory let's get our hands dirty.


Prerequisites

Before we begin, make sure you have the following:

  • An AWS account

  • AWS CLI.

  • Docker installed and running.

  • Basic knowledge of Python programming language

  • Basic understanding of AWS Lambda and Amazon S3


Note:
You will find the number 112233445566 in the commands below.
You will have to replace it with your AWS account id.

Setting up the AWS environment

To get started, follow these steps to set up your AWS environment:


1. Create an S3 bucket


Let's create the bucket that will be used to upload user images. For this example, we will use the same bucket to store the resized images but ideally, you should use another bucket. The reason is that the Lambda function will be invoked on



$ aws s3api create-bucket \
    --bucket awslambda-imageresizer-test-002 \
    --create-bucket-configuration "LocationConstraint=us-west-2"

2. Create an IAM role


We are creating a role and providing permission to the Lambda service assume this role.


$ aws iam create-role \
    --role-name awslambda-imageresizer-role \
    --assume-role-policy-document \
'{
  "Version": "2012-10-17",
  "Statement": [
  {
    "Effect": "Allow", 
    "Principal": {
      "Service": "lambda.amazonaws.com"
     }, 
     "Action": "sts:AssumeRole"
   }
  ]
}'

Let's add the AWSLambdaBasicExecutionRole policy. It provides write permissions to CloudWatch Logs.



$ aws iam attach-role-policy \
    --role-name awslambda-imageresizer-role \
    --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Now let's allow the role to get and put objects to our S3 bucket



$ aws iam put-role-policy \
    --role-name awslambda-imageresizer-role \
    --policy-name S3AccessPolicy --policy-document \
'{
    "Version": "2012-10-17",
    "Statement":[
    {
        "Effect": "Allow",
        "Action": [ "s3:PutObject", "s3:GetObject" ],
        "Resource": "arn:aws:s3:::awslambda-imageresizer-test-002/**"
     }
   ]
}'

Setting up the code


Get the code from this GitHub repo; phashish/aws-lambda-image-resizer



$ git clone git@github.com:phashish/aws-lambda-image-resizer.git 
$ cd aws-lambda-image-resizer/

Let's go through the important files here.

First is the Dockerfile.



$ cat Dockerfile

FROM public.ecr.aws/lambda/python:3.10
COPY requirements.txt ${LAMBDA_TASK_ROOT}
COPY lambda_function.py ${LAMBDA_TASK_ROOT}
RUN pip install -r requirements.txt
CMD [ "lambda_function.handler" ]

Here we are using AWS's Python 3.10 image.

Then copying our script and the requirements.txt.

Then using pip to install the boto library for connecting to S3 and Pillow to manipulate the image.


The second file is our code, where we are parsing the uploaded image details from the event.

Using boto3, we are open the image and resize it using the Pillow library.

Finally using boto3 to upload the resized image with a 'resized-' prefix attached.




$ cat lambda_function.py 

import boto3
from PIL import Image
import io

def handler(event, context):
    # Get the S3 bucket and key from the event
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    print("Got new image: " + key + " from the bucket: " + bucket)

    # Set the desired width and height for resizing
    width = 800
    height = 600

    # Load the image from S3
    s3 = boto3.client('s3')
    response = s3.get_object(Bucket=bucket, Key=key)
    image = Image.open(io.BytesIO(response['Body'].read()))

    # Resize the image
    resized_image = image.resize((width, height))
    print("Image resized.")

    # Save the resized image to the same S3 bucket with a different name
    resized_key = 'resized-' + key
    with io.BytesIO() as output:
        resized_image.save(output, format='JPEG')
        output.seek(0)
        s3.put_object(Body=output, Bucket=bucket, Key=resized_key)

    print("Image " + resized_key + " uploaded.")
    return {
        'statusCode': 200,
        'body': 'Image resized successfully!'
    }

The third important file is the requirements.txt



$ cat requirements.txt 

boto3
Pillow

Building the code


Let's log into the AWS ECR so that we can upload out image.



$ aws ecr --region us-west-2 \
    get-login-password | docker login \
    --username AWS --password-stdin \
    112233445566.dkr.ecr.us-west-2.amazonaws.com 

Create the repository for our image



$ aws ecr --region us-west-2 create-repository \
    --repository-name aws-lambda-image-resizer

Now let's build the code



$ docker build -t aws-lambda-image-resizer:test .

If there are any errors, fix them and get a successful build.

Now let's tag the build so that we can upload it



$ docker tag aws-lambda-image-resizer:test 112233445566.dkr.ecr.us-west-2.amazonaws.com/aws-lambda-image-resizer:latest

Let's push the image



$ docker push 112233445566.dkr.ecr.us-west-2.amazonaws.com/aws-lambda-image-resizer:latest

Now, let's create the Lambda function



$ aws lambda --region us-west-2 create-function \
    --function-name aws-lambda-image-resizer \
    --package-type Image \
    --code ImageUri=112233445566.dkr.ecr.us-west-2.amazonaws.com/aws-lambda-image-resizer:latest \
    --role arn:aws:iam::112233445566:role/awslambda-imageresizer-role

You can get information about the function



$ aws lambda --region us-west-2 get-function \
    --function-name aws-lambda-image-resizer 

{
    "Configuration": {
        "FunctionName": "aws-lambda-image-resizer",
        "FunctionArn": "arn:aws:lambda:us-west-2:112233445566:function:aws-lambda-image-resizer",
        "Role": "arn:aws:iam::112233445566:role/awslambda-imageresizer-role",
        "CodeSize": 0,
        "Description": "",
        "Timeout": 3,
        "MemorySize": 128,
        "LastModified": "2023-07-19T14:01:11.135+0000",
        "CodeSha256": "658764bc89cfa54c3f1649f263b4aca61a5aa9813ff0ca2e53ab1d3a111adb33",
        "Version": "$LATEST",
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "1d4f1de2-83d1-48da-b0fb-dc7c74095844",
        "State": "Active",
        "LastUpdateStatus": "Successful",
        "PackageType": "Image",
        "Architectures": [
            "x86_64"
        ],
        "EphemeralStorage": {
            "Size": 512
        },
        "SnapStart": {
            "ApplyOn": "None",
            "OptimizationStatus": "Off"
        }
    },
    "Code": {
        "RepositoryType": "ECR",
        "ImageUri": "112233445566.dkr.ecr.us-west-2.amazonaws.com/aws-lambda-image-resizer:latest",
        "ResolvedImageUri": "112233445566.dkr.ecr.us-west-2.amazonaws.com/aws-lambda-image-resizer@sha256:658764bc89cfa54c3f1649f263b4aca61a5aa9813ff0ca2e53ab1d3a111adb33"
    }
}
    

You can see that the default function timeout is 3 sec. This is really low. We know that our code will not be able to download, resize and upload an image in 3 sec.

So lets update the configuration.



$ aws lambda --region us-west-2 update-function-configuration \
    --function-name aws-lambda-image-resizer \
    --timeout 120

You can confirm the change by running the above get-function command again.

Now let's add the image upload event trigger in S3 to trigger our Lambda function.



$ aws lambda --region us-west-2 add-permission \
    --function-name aws-lambda-image-resizer \
    --action "lambda:InvokeFunction" \
    --principal s3.amazonaws.com \
    --source-arn arn:aws:s3:::awslambda-imageresizer-test-002 \
    --statement-id s3-trigger

Now we'll add the bucket notification configuration so that our Lambda function is notified when anyone uploads a file with prefix "test-" and suffix ".jpg"



$ aws s3api put-bucket-notification-configuration \
    --bucket awslambda-imageresizer-test-002 \
    --notification-configuration '{
    "LambdaFunctionConfigurations": [
      {
        "LambdaFunctionArn": "arn:aws:lambda:us-west-2:112233445566:function:aws-lambda-image-resizer",
        "Events": ["s3:ObjectCreated:*"],
        "Filter": {
          "Key": {
            "FilterRules": [
              {
                "Name": "prefix",
                "Value": "test-"
              },
              {
                "Name": "suffix",
                "Value": ".jpg"
              }
            ]
          }
        }
      }
    ]
  }'
 

Testing the setup


Now let's test our setup by uploading a file.



$ aws s3 cp test-wallpaper.jpg s3://awslambda-imageresizer-test-002/

upload: ./test-wallpaper.jpg to s3://awslambda-imageresizer-test-002/test-wallpaper.jpg

We will check the logs. In order to do that we need to find the Cloudwatch LogGroup



$ aws --region us-west-2 logs describe-log-groups

{
    "logGroups": [
        {
            "logGroupName": "/aws/lambda/aws-lambda-image-resizer",
            "creationTime": 1689843384125,
            "metricFilterCount": 0,
            "arn": "arn:aws:logs:us-west-2:112233445566:log-group:/aws/lambda/aws-lambda-image-resizer:*",
            "storedBytes": 0
        }
    ]
}

Now we can check the logs to see how our function ran.



$ aws --region us-west-2 logs \
    tail /aws/lambda/aws-lambda-image-resizer 

2023-07-20T08:56:23.440000+00:00 2023/07/20/[$LATEST]7a31213c928d4e58a8609655f34008d8 START RequestId: db0033a0-bad0-4d0a-8045-ba22b985450c Version: $LATEST
2023-07-20T08:56:23.441000+00:00 2023/07/20/[$LATEST]7a31213c928d4e58a8609655f34008d8 Got new image: test-wallpaper.jpg from the bucket: awslambda-imageresizer-test-002
2023-07-20T08:56:26.806000+00:00 2023/07/20/[$LATEST]7a31213c928d4e58a8609655f34008d8 Image resized.
2023-07-20T08:56:26.983000+00:00 2023/07/20/[$LATEST]7a31213c928d4e58a8609655f34008d8 Image resized-test-wallpaper.jpg uploaded.
2023-07-20T08:56:27.022000+00:00 2023/07/20/[$LATEST]7a31213c928d4e58a8609655f34008d8 END RequestId: db0033a0-bad0-4d0a-8045-ba22b985450c
2023-07-20T08:56:27.022000+00:00 2023/07/20/[$LATEST]7a31213c928d4e58a8609655f34008d8 REPORT RequestId: db0033a0-bad0-4d0a-8045-ba22b985450c	Duration: 3582.03 ms	Billed Duration: 5474 ms	Memory Size: 128 MB	Max Memory Used: 93 MB	Init Duration: 1891.44 ms

We will also check the S3 buckets to make sure that we have our resized image.



$ aws s3 ls s3://awslambda-imageresizer-test-002/

2023-07-20 14:26:27     136279 resized-test-wallpaper.jpg
2023-07-20 14:26:10    1233774 test-wallpaper.jpg

Voila! We have our resized image.

We can see the difference in the file size and the file creation time.

So this is how we can create a simple image resize function and run it on AWS Lambda.


Clean up


We will now remove all the resources we have created for this test.

First remove the log-group from Cloudwatch



$ aws --region us-west-2 logs delete-log-group \
    --log-group-name /aws/lambda/aws-lambda-image-resizer
    

Lets remove all the images from the S3 bucket and delete the bucket



$ aws s3 rm s3://awslambda-imageresizer-test-002/ --recursive 

delete: s3://awslambda-imageresizer-test-002/resized-test-wallpaper.jpg
delete: s3://awslambda-imageresizer-test-002/test-wallpaper.jpg

And the bucket itself



$ aws s3api delete-bucket \
    --bucket awslambda-imageresizer-test-002

Now we will remove the function



$ aws --region us-west-2 lambda delete-function \
    --function-name aws-lambda-image-resizer
    

We will now delete the ECR repository



$ aws --region us-west-2 ecr delete-repository \
    --repository-name aws-lambda-image-resizer --force

{
    "repository": {
        "repositoryArn": "arn:aws:ecr:us-west-2:112233445566:repository/aws-lambda-image-resizer",
        "registryId": "112233445566",
        "repositoryName": "aws-lambda-image-resizer",
        "repositoryUri": "112233445566.dkr.ecr.us-west-2.amazonaws.com/aws-lambda-image-resizer",
        "createdAt": "2023-07-19T19:26:31+05:30",
        "imageTagMutability": "MUTABLE"
    }
}

Now let's delete the S3AccessPolicy role policy



$ aws iam delete-role-policy \
    --role-name awslambda-imageresizer-role \
    --policy-name S3AccessPolicy

Now the attached AWSLambdaBasicExecutionPolicy



$ aws iam detach-role-policy \
    --role-name awslambda-imageresizer-role \
    --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    

And finally, the role



$ aws iam delete-role --role-name awslambda-imageresizer-role

Conclusion


In this blog post, we explored how to use Python and AWS Lambda to resize images dynamically. We set up the AWS environment, wrote the Python code using the Pillow library, deployed the Lambda function, and tested the image resizing functionality. With AWS Lambda, you can easily automate image resizing tasks and efficiently manage your infrastructure.


AWS Lambda provides many more features and capabilities, such as configuring memory and timeout settings, handling error cases, and integrating with other AWS services. I encourage you to explore the AWS Lambda documentation for more advanced usage scenarios.


Happy image resizing!

Recent Posts

See All

AWS IAM Concepts - Roles

When it comes to AWS IAM (Identity and Access Management), roles are one of the key building blocks. Roles are used to grant permissions...

Comments


bottom of page