Guide: AWS Lambda + Pillow for Complex Image Processing
If you run an e-commerce site, you know the pain: vendors upload images in every size and format imaginable, and someone has to resize them all to something consistent. You can do this manually, or you can let Lambda handle it.
This guide walks through setting up an image resizing function with Lambda and Pillow. You’ll need a basic understanding of Lambda and Python to follow along.
Step 1: Setting Up the Python Environment with Shared Libraries (Pillow)
Lambda needs Python libraries in a specific folder structure to find them at runtime. The path is ./python/lib/python3.10/site-packages/ for Python 3.10. If you’re using a different Python version, adjust accordingly.
Here’s how to set it up:
1a. Navigate to your project directory:
cd /path/to/your/project-folder
1b. Create the python folder:
mkdir python
1c. Set up the full path:
mkdir -p python/lib/python3.10/site-packages/
1d. Install Pillow into that path:
pip3 install pillow -t ./python/lib/python3.10/site-packages/
This puts Pillow right where Lambda expects it. You’ll zip the python folder later to create your Lambda Layer.
Step 2: Writing the Image Resizing Function
2a. Create the Lambda function file next to your python folder:
touch lambda_function.py
2b. Add this code to lambda_function.py:
import os
import boto3
from PIL import Image
def resize_image(image_path, resized_path):
with Image.open(image_path) as image:
image.thumbnail((800, 800))
image.save(resized_path)
def lambda_handler(event, context):
s3_client = boto3.client('s3')
for record in event['Records']:
bucket_name = record['s3']['bucket']['name']
key_name = record['s3']['object']['key']
download_path = '/tmp/{}'.format(os.path.basename(key_name))
upload_path = '/tmp/resized-{}'.format(os.path.basename(key_name))
s3_client.download_file(bucket_name, key_name, download_path)
resize_image(download_path, upload_path)
s3_client.upload_file(upload_path, bucket_name, 'resized-{}'.format(key_name))
The handler listens for S3 events. When an image lands in your bucket, it downloads to /tmp/, resizes it using Pillow’s thumbnail method (keeping it under 800 pixels on the longest side), and uploads the result back to the same bucket with a resized- prefix.
Step 3: Creating the Lambda Layer for Pillow
Now bundle the Pillow library into a Layer that your function can use.
For a deeper dive on packaging dependencies as layers including custom runtimes, see Lambda Layers and Custom Runtimes. If layers feel limiting, Lambda container images remove the 250MB constraint entirely.
Zip the python folder:
zip -r pillow_layer.zip python
This creates a deployable Layer package.
Step 4: Deploying the Lambda Function and Layer
4a. Open the Lambda console and go to Layers.
4b. Create a new Layer named PillowLayer, upload pillow_layer.zip, and set Python 3.10 as the compatible runtime.
4c. Create your Lambda function. Pick Python 3.10 as the runtime.
4d. Add an S3 trigger to watch your image upload bucket.
4e. Attach the PillowLayer you created by clicking “Add Layer” in the function designer.
4f. Upload your lambda_function.py code and make sure your IAM role can read from and write to the S3 bucket.
Save and you’re done with the AWS setup.
The Shared Library Issue You Might Hit
Pillow depends on libz.so.1, and Lambda’s default environment doesn’t always have the right version. If you see an error about ZLIB_1.2.9 not found, that’s the culprit.
The fix: include the required shared library in your deployment.
Step 5A: Adding the Missing Library
5a-1. Create a lib folder in your project root:
mkdir lib
5a-2. Download or compile libz.so.1 for Amazon Linux 2, then place it in the lib folder.
Add the Environment Variable
Set LD_LIBRARY_PATH to include /var/task/lib. This tells Python where to find the shared objects it needs at runtime. You can set this in the Lambda function’s environment variables section.
Step 5: Testing
Drop an image into your S3 bucket. Lambda should pick it up, resize it, and put a resized- prefixed version back in the same bucket. Check that the output exists and has the expected dimensions.
That’s it. Lambda plus Pillow handles the image processing without you having to run a server.
A note on Python versions: This guide uses Python 3.10 because that was the current runtime at the time of writing. AWS Lambda now supports Python 3.12, 3.13, and 3.14 as well. If you’re starting fresh, consider using a newer version. The package installation command has also been updated in Lambda’s docs - you may want to use the --platform manylinux2014_x86_64 --only-binary=:all: flags to grab pre-compiled binaries and avoid C extension compilation headaches.
Comments