AWS S3 Cost Optimization: Automate Cleanup of Abandoned Buckets
This blog explores an event-driven architecture that automatically identifies and cleans up abandoned S3 buckets to optimize AWS costs

๐โโ๏ธ Introduction
Hey folks! I'm Ankit, working as a Kubernetes Engineer at CirrOps and a newly minted AWS Community Builder. Iโm passionate about Cloud and Container technologies. But today, I'm switching gears to talk about something equally important - Cost Optimization, more specifically about AWS S3 buckets.
๐ Synopsis
- In most projects, we spin up a large number of AWS S3 buckets while developing an app or testing, or running in production. It's often the go-to solution for media-related applications. But the problem here is, when testing applications or in production environments, we end up creating tons of buckets that eventually get abandoned by users, testers, employees, or applications.
- These buckets not only lead to unnecessary costs but also create a management mess. Deleting those buckets by hand is boring, easy to mess up, and frankly, the last thing anyone wants to do on a Friday evening. So I spent a weekend putting together an eventโdriven cleanup workflow that removes or retains buckets automatically for us
๐คฉ TL;DR (The โ30โSecondโ Version)
Condition | Action |
---|---|
Bucket not accessed in the last N days and tag autoDelete=True |
Delete bucket (all versions, then the bucket itself) |
Bucket not accessed in the last N days and tag autoDelete=False |
Ignore IT and leave it as it is |
Bucket not accessed in the last N days and no valid tag | Notify user โ URI to Keep (adds tag) or Delete (deletes bucket) |
๐กHigh-Level Solution
Imagine every bucket in your account carries a simple tag:
autoDelete=True
orautoDelete=False
A scheduled job or script runs daily and checks each bucketโs last access date. If a bucket hasnโt seen any activity in the last 30 days and its tag is:
- True โ Automatically delete the bucket and all of its contents.
- False โ Leave the bucket alone.
- Missing or invalid โ Send the bucket owner an email with two options:
- Keep it (tag it
autoDelete=False
) - Delete it (confirm deletion)
- Keep it (tag it
This approach makes sure that we only remove truly abandoned buckets and gives users one-click control over exceptions.
โ Prerequisites
- ๐ An AWS account with administrative privileges
- ๐ Basic familiarity with Python and Boto3
- ๐ Understanding of AWS Lambda, EventBridge, SNS, API Gateway, and DynamoDB
๐ฆ List of AWS services
- ๐ชฃ Amazon S3
- ๐จ AWS SNS
- โ CloudTrail
- ๐ DynamoDB
- ๐ฅ๏ธ AWS Lambda
- ๐ AWS EventBridge
- ๐ Amazon API Gateway
๐ฏ Architecture
Let's dive into how this all works together.

A key question you might have is: How can we determine when a bucket was last accessed? There are several approaches, but I personally prefer using a combination of CloudTrail, Lambda, and DynamoDB
Here's the breakdown of the architecture:
โ CloudTrail:
- AWS doesnโt expose LastAccessed for a bucket out of the box, but CloudTrail records every objectโlevel API call.
- I configured a dataโevent trail (yes, it costs a little extra) and pointed it at the logging bucket
s3EventLoggingStorage
- Each time an object is listed, uploaded, downloaded, or deleted, the event lands in EventBridge and triggers s3EventLogger, which writes:
{
"BucketName" xyz-terraform: ,
"EventDateTime": 2024-07-24T00:41:03Z ,
"EventName": ListObjects ,
"EventDate": 2024-07-24,
"EventTime": 00:41:03Z,
"Status": Active
}
๐ EventBridge:
EventBridge has two important components in our solution:
- Rule: Created a rule in the event bus with an event pattern that captures every S3 bucket event (except for our CloudTrail logging bucket
s3EventLoggingStorage
)- This rule triggers a Lambda function named
s3EventLogger
when the pattern matches. - The event pattern configuration looks like this:
- This rule triggers a Lambda function named
{
"source": ["aws.s3"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["s3.amazonaws.com"],
"eventName": ["ListObjects", "ListObjectVersions", "PutObject", "GetObject", "HeadObject", "CopyObject", "GetObjectAcl", "PutObjectAcl", "CreateMultipartUpload", "ListParts", "UploadPart", "CompleteMultipartUpload", "AbortMultipartUpload", "UploadPartCopy", "RestoreObject", "DeleteObject", "DeleteObjects", "GetObjectTorrent", "SelectObjectContent", "PutObjectLockRetention", "PutObjectLockLegalHold", "GetObjectLockRetention", "GetObjectLockLegalHold"],
"requestParameters": {
"bucketName": [{
"anything-but": ["s3EventLoggingStorage"]
}]
}
}
}
- Scheduler: Executes the
s3Scanner
Lambda function daily at 9:00 AM
๐ฅ๏ธ Lambda
- We have a total of 3 buckets working together:
- 1)
s3EventLogger
:- Primary purpose: Record when any bucket receives any kind of API call
- Collects event data such as BucketName, EventDate etc..
- Stores this data in a DynamoDB table called
s3DateLogger
- 2)
s3Scanner
:- Triggered every day at 9:00 AM UTC via an EventBridge schedule.
- Lists all buckets in the account.
- For each bucket:
- Fetch the last access date from DynamoDB (if no record exists in DynamoDB, then create an entry and set the date to 15 days in the future as a grace period - useful if you have older buckets)
- Calculate days since the last access
- Retrieve the
autoDelete
tag (if any)- Decision logic:
autoDelete=True
&& days โฅ 30 โ Delete all object versions, then the bucketautoDelete=False
&& days โฅ 30 โ Skip deletion
- Decision logic:
- No valid tag && days โฅ 30 โ Publish a notification to SNS
- 3)
userHandler
:- It will be triggered by the API Gateway
- It either deletes the bucket or adds the
autoDelete=False
tag based on user choice
๐ง SNS:
- SNS sends an email to the user with two links:
- Keep It: Calls the API Gateway endpoint
?bucket_name={bucket_name}&action=keep
- Delete It: Calls the API Gateway endpoint
?{bucket_name}&action=delete
- Keep It: Calls the API Gateway endpoint
๐ API Gateway:
- Provides endpoints for users to respond to notifications, triggering the
userHandler
Lambda
๐งโ๐ป Source Code
The GitHub repository contains:
- Link: Source code for all three Lambda functions
- Note: This was a weekend project, so the code has room for improvement. If youโre a beginner, try extending it to fit your own environment. ๐
๐ Conclusion
We explored how to automate S3 cleanup with minimal human intervention and potentially save a bunch of dollars in the process. Give it a try over the weekend or at your convenience. I hope you enjoyed this blog as much as I enjoyed creating it.
And thatโs a wrap! ๐๐ฅ
If you liked my work, please message me on LinkedIn with "Hi + your country name"
- ๐โโ๏ธ Ankit Jodhani
๐จ Reach me at ankitjodhani1903@gmail.com