AWS Cost Optimization in Action: Auto-Delete Old EBS Snapshots

Titus JamesTitus James
4 min read

If you’ve ever launched an EC2 instance on AWS, chances are you’ve created EBS snapshots—backups of your volumes that let you recover data later. These snapshots are great for disaster recovery, but they have a sneaky side effect: they stick around even after the volume or EC2 instance is gone, silently adding to your AWS bill.

What if we could automatically detect and delete unused (stale) EBS snapshots?

That’s exactly what I did using AWS Lambda, Python (Boto3), and a bit of automation magic. In this post, I’ll show you how to build it yourself.


Step 1: Understand What an EBS Snapshot Is

When you create an EC2 instance, AWS automatically attaches an EBS volume to it (usually for your OS). From the EC2 dashboard, you can:

  • View that volume under "Volumes"

  • Create a snapshot by selecting it and choosing "Create Snapshot"

A snapshot is just a copy of that volume at a moment in time. You can later use it to create new volumes or restore deleted ones.

But here’s the catch: if you delete the EC2 instance or volume, AWS does not delete the snapshot. You continue paying for it even if you don’t need it anymore.


Step 2: Automate Snapshot Cleanup with AWS Lambda

Let’s automate the process of deleting snapshots that are no longer associated with any active EC2 instance or volume.

Here’s the plan:

  • Write a Lambda function in Python using Boto3

  • Give it the necessary permissions

  • Test it on your account

  • Schedule it using CloudWatch


Writing the Lambda Function

Go to the AWS Lambda console and create a new function.
Choose:

  • Author from scratch

  • Python 3.11 as the runtime

Paste this code in the editor:

import boto3

def lambda_handler(event, context):
    ec2 = boto3.client('ec2')

    # Get all EBS snapshots
    response = ec2.describe_snapshots(OwnerIds=['self'])

    # Get all active EC2 instance IDs
    instances_response = ec2.describe_instances(Filters=[{'Name': 'instance-state-name', 'Values': ['running']}])
    active_instance_ids = set()

    for reservation in instances_response['Reservations']:
        for instance in reservation['Instances']:
            active_instance_ids.add(instance['InstanceId'])

    # Iterate through each snapshot and delete if it's not attached to any volume or the volume is not attached to a running instance
    for snapshot in response['Snapshots']:
        snapshot_id = snapshot['SnapshotId']
        volume_id = snapshot.get('VolumeId')

        if not volume_id:
            # Delete the snapshot if it's not attached to any volume
            ec2.delete_snapshot(SnapshotId=snapshot_id)
            print(f"Deleted EBS snapshot {snapshot_id} as it was not attached to any volume.")
        else:
            # Check if the volume still exists
            try:
                volume_response = ec2.describe_volumes(VolumeIds=[volume_id])
                if not volume_response['Volumes'][0]['Attachments']:
                    ec2.delete_snapshot(SnapshotId=snapshot_id)
                    print(f"Deleted EBS snapshot {snapshot_id} as it was taken from a volume not attached to any running instance.")
            except ec2.exceptions.ClientError as e:
                if e.response['Error']['Code'] == 'InvalidVolume.NotFound':
                    # The volume associated with the snapshot is not found (it might have been deleted)
                    ec2.delete_snapshot(SnapshotId=snapshot_id)
                    print(f"Deleted EBS snapshot {snapshot_id} as its associated volume was not found.")

Click Deploy when done.


Add Permissions for Lambda

If you test this Lambda now, it’ll likely fail. That’s because it doesn’t yet have permission to:

  • Describe or delete snapshots

  • Access volumes or instances

Here’s how to fix that:

  1. In the Lambda page, under Configuration → Permissions, click the Execution Role

  2. Go to IAM → Create a new policy

  3. Choose EC2 as the service and give these actions:

    • DescribeInstances

    • DescribeVolumes

    • DescribeSnapshots

    • DeleteSnapshot

  4. Set the resource to * (all resources) and name the policy something like cost_optimization_ebs

  5. Attach the policy to your Lambda role

Now your Lambda has everything it needs.


Increase Lambda Timeout (Just a Little)

Go to your Lambda’s General configuration and set the timeout to 10 seconds.
Lambda pricing depends on runtime duration, so keep it minimal—just enough to finish the task.


Step 3: Test It

Now let’s see it in action.

  1. Delete your EC2 instance (and confirm the volume is gone)

  2. The snapshot you created earlier still exists

  3. Run your Lambda function again

  4. The snapshot should be deleted!

Why? Because its associated volume no longer exists, and the function is designed to clean up exactly this kind of leftover.


How the Code Works (In Simple Terms)

  • It checks all your EC2 instances and stores the ones currently running

  • It looks through all your snapshots

  • For each snapshot:

    • If the related volume doesn’t exist → delete snapshot

    • If the volume exists but is not attached → delete snapshot

    • If the volume is attached to a stopped or terminated instance → delete snapshot

Simple logic, powerful results.


Schedule with CloudWatch (Optional)

Want to run this every day automatically?

  1. Go to Amazon EventBridge (CloudWatch Events)

  2. Create a new rule

  3. Use a fixed schedule like rate(1 day)

  4. Set your Lambda function as the target


Wrap-Up

By cleaning up unused EBS snapshots, you can save money, reduce clutter, and automate part of your cloud management.

All it takes is:

  • One Lambda function

  • A bit of IAM setup

  • Scheduling

It’s a great example of how a small automation can have a big financial impact in the long run.

0
Subscribe to my newsletter

Read articles from Titus James directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Titus James
Titus James