Image Processing Serverless using AWS Lambda
Using Terraform
Project Architecture

The lambda function will do the image processing.
The Source bucket has the files, and then it will be passed to the Lambda function. The lambda function will convert the image into 5 different formats.
The output will be passed down to the destination bucket.
Monitoring also exists with CloudWatch.
Random ID creation for unique names
resource "random_id" "suffix" {
byte_length = 4
}
locals {
bucket_prefix = "${var.project_name}-${var.environment}"
upload_bucket_name = "${local.bucket_prefix}-upload-${random_id.suffix.hex}"
processed_bucket_name = "${local.bucket_prefix}-processed-${random_id.suffix.hex}"
lambda_function_name = "${var.project_name}-${var.environment}-processor"
}
S3 Buckets - Source and Destination
Source
# S3 Bucket for uploading original images (SOURCE)
resource "aws_s3_bucket" "upload_bucket" {
bucket = local.upload_bucket_name # taken from the unique id we created above
}
resource "aws_s3_bucket_versioning" "upload_bucket" {
bucket = aws_s3_bucket.upload_bucket.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "upload_bucket" {
bucket = aws_s3_bucket.upload_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "upload_bucket" {
bucket = aws_s3_bucket.upload_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Destination
# S3 Bucket for processed images (DESTINATION)
resource "aws_s3_bucket" "processed_bucket" {
bucket = local.processed_bucket_name
}
resource "aws_s3_bucket_versioning" "processed_bucket" {
bucket = aws_s3_bucket.processed_bucket.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "processed_bucket" {
bucket = aws_s3_bucket.processed_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "processed_bucket" {
bucket = aws_s3_bucket.processed_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
IAM Role and Policy for Lambda
resource "aws_iam_role" "lambda_role" {
name = "${local.lambda_function_name}-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "lambda_policy" {
name = "${local.lambda_function_name}-policy"
role = aws_iam_role.lambda_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${var.aws_region}:*:*"
},
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:GetObjectVersion"
]
Resource = "${aws_s3_bucket.upload_bucket.arn}/*"
},
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:PutObjectAcl"
]
Resource = "${aws_s3_bucket.processed_bucket.arn}/*"
}
]
})
}
Pillow - Image Processing
resource "aws_lambda_layer_version" "pillow_layer" {
filename = "${path.module}/pillow_layer.zip"
layer_name = "${var.project_name}-pillow-layer"
compatible_runtimes = ["python3.12"]
description = "Pillow library for image processing"
}
Data Source
data "archive_file" "lambda_zip" {
type = "zip"
source_file = "${path.module}/../lambda/lambda_function.py"
output_path = "${path.module}/lambda_function.zip"
}
resource "aws_lambda_function" "image_processor" {
filename = data.archive_file.lambda_zip.output_path
function_name = local.lambda_function_name
role = aws_iam_role.lambda_role.arn
handler = "lambda_function.lambda_handler"
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
runtime = "python3.12"
timeout = 60
memory_size = 1024
layers = [aws_lambda_layer_version.pillow_layer.arn]
environment {
variables = {
PROCESSED_BUCKET = aws_s3_bucket.processed_bucket.id
LOG_LEVEL = "INFO"
}
}
}
CloudWatch for Logs
resource "aws_cloudwatch_log_group" "lambda_processor" {
name = "/aws/lambda/${local.lambda_function_name}"
retention_in_days = 7
}
S3 Event Trigger
resource "aws_lambda_permission" "allow_s3" {
statement_id = "AllowExecutionFromS3"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.image_processor.function_name
principal = "s3.amazonaws.com"
source_arn = aws_s3_bucket.upload_bucket.arn
}
resource "aws_s3_bucket_notification" "upload_bucket_notification" {
bucket = aws_s3_bucket.upload_bucket.id
lambda_function {
lambda_function_arn = aws_lambda_function.image_processor.arn
events = ["s3:ObjectCreated:*"]
}
depends_on = [aws_lambda_permission.allow_s3]
}
Any issues with publishlayeraccess?
Add this inline policy via the AWS CLI or the IAM console.
This permission is required for the IAM principal running Terraform, not for the Lambda execution role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:PublishLayerVersion"
],
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:layer:*" # replace with your values
}
]
}
Working
Go to the S3 source bucket and upload the file. Then, after the function completes, the processed bucket gives the output files.

Reference
Arigato!