On this page
article
Project: Serverless Image Processor
Build an AWS Lambda function that resizes images uploaded to S3 — event-driven architecture with boto3 and Pillow.
Build a serverless pipeline: upload an image to S3 → Lambda triggers → creates a thumbnail → saves to another bucket.
Architecture
User uploads image.jpg → S3 (source bucket)
↓ event trigger
Lambda function
↓
S3 (destination bucket/thumbnails/)
Prerequisites
- AWS account with Lambda and S3 access
- AWS CLI configured (
aws configure) - Python 3.12
Lambda Function
# lambda_function.py
import json
import os
import boto3
from io import BytesIO
from PIL import Image
s3 = boto3.client("s3")
DEST_BUCKET = os.environ["DEST_BUCKET"]
THUMBNAIL_SIZE = (200, 200)
def lambda_handler(event, context):
for record in event["Records"]:
source_bucket = record["s3"]["bucket"]["name"]
source_key = record["s3"]["object"]["key"]
print(f"Processing s3://{source_bucket}/{source_key}")
response = s3.get_object(Bucket=source_bucket, Key=source_key)
image_data = response["Body"].read()
image = Image.open(BytesIO(image_data))
image.thumbnail(THUMBNAIL_SIZE)
buffer = BytesIO()
image.save(buffer, format="JPEG", quality=85)
buffer.seek(0)
dest_key = f"thumbnails/{source_key}"
s3.put_object(
Bucket=DEST_BUCKET,
Key=dest_key,
Body=buffer,
ContentType="image/jpeg",
)
print(f"Saved thumbnail to s3://{DEST_BUCKET}/{dest_key}")
return {
"statusCode": 200,
"body": json.dumps({"message": "Processing complete"}),
}
Dependencies Layer
Lambda doesn’t include Pillow by default. Build a layer:
mkdir -p layer/python
pip install Pillow boto3 -t layer/python/
cd layer && zip -r ../pillow-layer.zip python && cd ..
Deploy
# Create buckets
aws s3 mb s3://my-image-source-bucket
aws s3 mb s3://my-image-dest-bucket
# Package function
zip function.zip lambda_function.py
# Create Lambda
aws lambda create-function \
--function-name image-resizer \
--runtime python3.12 \
--handler lambda_function.lambda_handler \
--role arn:aws:iam::ACCOUNT:role/lambda-s3-role \
--zip-file fileb://function.zip \
--environment "Variables={DEST_BUCKET=my-image-dest-bucket}" \
--layers arn:aws:lambda:REGION:ACCOUNT:layer:pillow-layer:1 \
--timeout 30 \
--memory-size 256
S3 Trigger
aws s3api put-bucket-notification-configuration \
--bucket my-image-source-bucket \
--notification-configuration '{
"LambdaFunctionConfigurations": [{
"LambdaFunctionArn": "arn:aws:lambda:REGION:ACCOUNT:function:image-resizer",
"Events": ["s3:ObjectCreated:*"],
"Filter": {"Key": {"FilterRules": [{"Name": "suffix", "Value": ".jpg"}]}}
}]
}'
Test
aws s3 cp photo.jpg s3://my-image-source-bucket/photo.jpg
aws s3 ls s3://my-image-dest-bucket/thumbnails/
IAM Permissions Required
The Lambda execution role needs:
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-image-source-bucket/*"
},
{
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::my-image-dest-bucket/*"
}
Concepts Applied
- AWS Lambda Getting Started
- Lambda + API Gateway
- File Handling / Binary
- Error Handling
- DevOps & CI/CD
Bonus Challenges
- Support PNG and WebP formats
- Generate multiple sizes (small, medium, large)
- Add error handling with Dead Letter Queue (DLQ)
- Add CloudWatch alarms for failures
- Deploy with AWS SAM or Terraform
- Add image metadata extraction (EXIF data)
This project demonstrates event-driven serverless architecture — a core pattern in modern cloud applications.