Automate Business Backups to S3
Write a Python script that backs up a folder of files to Amazon S3.
Introduction
30 Second Summary
Every business has files it can't afford to lose. Quarterly reports, employee records, meeting notes. Backing them up by hand is tedious and easy to forget.
In this project, you will build a Python script that automatically backs up a folder of business files to Amazon S3. You'll learn Python fundamentals like functions, loops, and error handling by applying them to real cloud automation.
What You'll Build
Run your finished script and watch it connect to AWS, create a cloud storage bucket, upload an entire folder of documents, and generate a backup report with file sizes and timestamps.
By the end of this project, you'll have:
- A Python backup script you can point at any folder to upload its contents to Amazon S3 automatically.
- A generated backup report showing every backed-up file with its name, size, and last-modified timestamp.
- Error handling that catches real AWS errors instead of crashing, plus a cleanup routine that safely removes all cloud resources to avoid charges.
- An optional Secret Mission to push your skills further.
Are there any prerequisites?
You'll need an AWS account with console access, Python 3.10 or higher installed on your machine, and a code editor like VS Code or Cursor.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your Tools
Your Python script will talk to AWS to create buckets, upload files, and manage cloud storage. For that to work, your machine needs two things: the AWS CLI (so your computer can authenticate with AWS) and boto3 (the Python library that sends commands to AWS services).
In this step, you'll install both tools, connect them to your AWS account, and create a project folder to hold your backup script.
In this step, get ready to:
- Install the AWS CLI and verify it works.
- Configure your AWS credentials so Python can access your account.
- Install boto3 and create your project folder.
Install the AWS CLI
The AWS CLI is a command-line tool that lets you interact with AWS services from your terminal. Your Python script relies on the credentials that the AWS CLI stores on your machine.
- Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check whether the AWS CLI is already installed by running this command:
aws --version
✔️ I see aws-cli/2.x
The AWS CLI v2 is already installed. You're all set for the next substep.
ⓧ I see aws-cli/1.x
You have an older version of the AWS CLI. You need version 2 for this project. Upgrade by following the platform-specific instructions below, then re-run aws --version to confirm version 2 is installed.
macOS
- Install AWS CLI v2 by running this command:
curl -fsSL https://awscli.amazonaws.com/v2/install.sh | bash
- Close and reopen your terminal, then run aws --version again. You should see aws-cli/2.x.x in the output.
Windows
- Open PowerShell as Administrator and install AWS CLI v2 by running this command:
irm 'https://awscli.amazonaws.com/v2/install.ps1' | iex
- Close and reopen PowerShell, then run aws --version again. You should see aws-cli/2.x.x in the output.
ⓧ Command not found
The AWS CLI is not installed yet. Follow the platform-specific instructions below to install it.
macOS
- Install AWS CLI v2 by running this command:
curl -fsSL https://awscli.amazonaws.com/v2/install.sh | bash
- Close and reopen your terminal, then verify the install by running:
aws --version
You should see something like aws-cli/2.x.x in the output.
Windows
- Open PowerShell as Administrator and install AWS CLI v2 by running this command:
irm 'https://awscli.amazonaws.com/v2/install.ps1' | iex
- Close and reopen PowerShell, then verify the install by running:
aws --version
You should see something like aws-cli/2.x.x in the output.
Still not seeing a version number?
Make sure you fully closed and reopened your terminal after installing. Some systems need a fresh session to pick up newly installed commands.
On macOS, if the command is still not found after reopening, you may need to add the install location to your PATH. Run export PATH="$HOME/.local/bin:$PATH" and try again.
Help me troubleshoot my AWS CLI installation.
Configure your AWS credentials
The AWS CLI needs your account credentials so it can make API calls on your behalf. You'll provide an Access Key ID and Secret Access Key, which act like a username and password for programmatic access.
Do you already have an AWS access key?
If you already have an Access Key ID and Secret Access Key saved, skip ahead to the aws configure step below.
If you need to create one, follow the instructions in the next few bullets. You only need to do this once.
- Open the IAM console in your browser by navigating to https://console.aws.amazon.com/iam/.
- In the left navigation pane, click Users.
- Click your username to open your user details page.
- Select the Security credentials tab.
- In the Access keys section, click Create access key.
- On the use case page, select Command Line Interface (CLI), then click Next.
- Skip the optional tag and click Create access key.
- Click Download .csv file to save your Access Key ID and Secret Access Key. Store this file somewhere safe.
- Click Done.
This is your only chance to see the secret key
AWS shows your Secret Access Key only once, at the moment you create it. If you lose it, you'll need to delete the key and create a new one. Save the .csv file before closing the page.
Now connect the AWS CLI to your account using those credentials.
- Configure the AWS CLI by running this command:
aws configure
What will this command ask me?
The aws configure command prompts you for four values, one at a time. Enter each one and press Enter:
- AWS Access Key ID - paste the Access Key ID from your downloaded .csv file.
- AWS Secret Access Key - paste the Secret Access Key from the same file.
- Default region name - type us-east-1.
- Default output format - type json.
- Verify your credentials are working by running this command:
aws sts get-caller-identity
You should see a JSON response with your AWS account number, user ID, and ARN. This confirms your CLI is connected to your account.
Seeing an error about credentials?
Double-check that you pasted the Access Key ID and Secret Access Key correctly with no extra spaces. Re-run aws configure to enter them again.
If you see a message about expired credentials, your access key may have been deactivated. Create a new one in the IAM console.
Help me fix my AWS CLI credentials.
Install boto3 and create your project folder
With the AWS CLI connected, the last setup piece is boto3. This is the Python library that translates your Python function calls into AWS API requests.
- Install boto3 by running this command:
pip install boto3
You should see output ending with a line like Successfully installed boto3-... confirming the package was installed.
pip command not found?
Try pip3 install boto3 instead. Some systems use pip3 for Python 3 packages.
Help me install boto3.
- Before you run this, what version number do you think boto3 will print?
- Verify that boto3 installed correctly by running this command:
python -c "import boto3; print(boto3.__version__)"
You should see a version number printed, such as 1.43.77. Any version number confirms boto3 is ready to use.
Seeing a ModuleNotFoundError?
If python opens the wrong version, try python3 instead. Make sure you installed boto3 with the same Python version you're using to run the command.
Help me verify my boto3 installation.
Now create a project folder to hold your backup script.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir s3-backup-tool
cd s3-backup-tool
- Confirm the folder was created by running:
ls
The folder will be empty for now. That's expected.
- Open s3-backup-tool in your code editor. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code (or Cursor) and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the s3-backup-tool folder, and click Open.
Why boto3 and the AWS CLI?
The AWS CLI stores your credentials in a file on your machine. When your Python script uses boto3, it automatically reads those stored credentials. You don't need to hard-code any passwords or keys into your code.
This separation keeps your script portable. Anyone with their own AWS credentials configured can run it without editing any code.
Your tools are locked and loaded. Next up, you'll write your first Python code that actually reaches out to AWS and creates a storage bucket in the cloud.
Connect to AWS and Create Your Backup Bucket
Your tools are installed and your AWS credentials are configured. Now it's time to see if Python can actually talk to AWS.
Before building any automation, you need proof that your code can reach the cloud. In this step, you'll write a Python script that connects to Amazon S3, creates a storage bucket, and uploads a test file.
In this step, get ready to:
- Connect Python to AWS and list your existing S3 buckets.
- Create a new S3 bucket with error handling.
- Upload a test file to your bucket to confirm everything works.
Create your script and connect to AWS
Your script needs a way to talk to AWS. The boto3 library provides an S3 client that maps directly to AWS API calls and returns Python dictionaries you can inspect.
- In your code editor, create a new file called s3_backup.py inside your s3-backup-tool folder.
- Paste the following code into s3_backup.py to set up your imports, constants, and S3 client function:
import boto3
import os
import sys
from datetime import datetime
from botocore.exceptions import ClientError
# Bucket name uses a timestamp so it's globally unique
BUCKET_NAME = "my-business-backup-" + datetime.now().strftime("%Y%m%d%H%M%S")
BACKUP_FOLDER = "business_files"
REGION = "us-east-1"
def create_s3_client():
"""Create and return an S3 client."""
s3_client = boto3.client("s3", region_name=REGION)
return s3_client
What does this code do?
The first five lines import the libraries your script needs. boto3 is the AWS SDK for Python. os handles file paths. sys lets you exit on errors. datetime generates timestamps. ClientError catches AWS-specific errors.
The three constants set your bucket name (with a unique timestamp suffix), the folder you'll back up later, and your AWS region. BUCKET_NAME uses a timestamp so it's globally unique every time you run the script.
The create_s3_client() function creates a boto3 S3 client pointed at us-east-1. This client is the object you'll use to call every S3 operation.
Why client instead of resource?
The client interface maps directly to AWS API calls and returns dictionaries. This teaches you to parse real API responses. The resource interface hides those details behind a higher-level abstraction.
- Add the list_buckets() function and a main block below create_s3_client() by pasting this code:
def list_buckets(s3_client):
"""List all S3 buckets in the account."""
response = s3_client.list_buckets()
buckets = response["Buckets"]
print(f"\nYou have {len(buckets)} bucket(s):")
for bucket in buckets:
print(f" - {bucket['Name']}")
return buckets
if __name__ == "__main__":
print("=== S3 Backup Manager ===\n")
# Create the S3 client and confirm connection
s3 = create_s3_client()
print("Connected to AWS S3!")
list_buckets(s3)
What does this code do?
The list_buckets() function calls the S3 API and parses the response dictionary. It reads the "Buckets" key to get a list of bucket objects, then loops through them to print each name.
The if __name__ == "__main__" block is where your script's logic runs. It creates the S3 client, prints a connection confirmation, and lists all buckets in your account.
Before you run this, what do you think will happen? Will Python be able to reach AWS and print your buckets?
- Save s3_backup.py.
- Run your script by entering this command in your terminal:
python s3_backup.py
You should see === S3 Backup Manager === followed by Connected to AWS S3! and a list of any existing buckets in your account.
Seeing a credentials error?
- Make sure your AWS CLI is configured. Run aws configure to check that your Access Key ID, Secret Access Key, region (us-east-1), and output format (json) are set.
- Confirm boto3 is installed by running pip install boto3 again.
- Check that your terminal is open inside the s3-backup-tool folder.
Still stuck? Help me debug my AWS connection error in s3_backup.py
Add a function to create your backup bucket
Listing buckets proves connectivity. Now your script needs to create its own bucket to store backups in.
- In s3_backup.py, add the create_bucket() function between list_buckets() and the if __name__ block:
def create_bucket(s3_client, bucket_name):
"""Create a new S3 bucket."""
try:
s3_client.create_bucket(Bucket=bucket_name)
print(f"\nBucket '{bucket_name}' created successfully!")
except ClientError as e:
print(f"\nError creating bucket: {e}")
sys.exit(1)
What does this code do?
The create_bucket() function calls s3_client.create_bucket(Bucket=bucket_name) to create a new S3 bucket. The try/except block catches any ClientError from AWS (like a duplicate bucket name) and prints the error instead of crashing.
Since you're using us-east-1 as your region, you do not need to specify a LocationConstraint. S3 creates buckets in us-east-1 by default when no constraint is given.
- In the if __name__ block, find this line:
list_buckets(s3)
- Add the following line directly below it, so your main block calls create_bucket() after listing:
create_bucket(s3, BUCKET_NAME)
- Save s3_backup.py and run it again:
python s3_backup.py
You should see the connection message, your bucket list, and then a new line confirming your bucket was created (e.g. Bucket 'my-business-backup-20260821143052' created successfully!).
Seeing an error about bucket names?
- S3 bucket names must be globally unique across all AWS accounts. If you see a naming conflict, the timestamp suffix in BUCKET_NAME should prevent this. Try running the script again (the timestamp changes each second).
- Make sure your Access Key ID has permission to create S3 buckets.
Need more help? Help me fix my S3 bucket creation error
Upload a test file to your bucket
Your bucket exists in the cloud. Now you'll upload a file to prove the full round trip works: local file to S3.
- Create a new file called test.txt inside your s3-backup-tool folder.
- Add this line to test.txt:
This is a test file for S3 backup.
- Save test.txt.
- Confirm the file exists by running:
ls
You should see both s3_backup.py and test.txt listed.
- Back in s3_backup.py, add the upload_file() function between create_bucket() and the if __name__ block:
def upload_file(s3_client, file_path, bucket_name):
"""Upload a single file to S3."""
# Extract just the filename from the full path
object_name = os.path.basename(file_path)
try:
s3_client.upload_file(file_path, bucket_name, object_name)
print(f" Uploaded: {object_name}")
except ClientError as e:
print(f" Error uploading {object_name}: {e}")
What does this code do?
The upload_file() function extracts the filename from the path using os.path.basename() and uploads it to S3 using s3_client.upload_file(). The three arguments are the local file path, the target bucket name, and the object key (filename) in S3.
Like create_bucket(), it uses try/except to catch upload errors gracefully instead of crashing the script.
- In the if __name__ block, find this line:
create_bucket(s3, BUCKET_NAME)
- Add these two lines directly below it:
upload_file(s3, 'test.txt', BUCKET_NAME)
print("Test file uploaded successfully!")
Before you run this, what do you think will happen? The script will create a brand new bucket and try to upload test.txt into it. Do you think it will succeed?
- Save s3_backup.py and run it:
python s3_backup.py
You should see Connected to AWS S3!, your bucket list, a bucket creation confirmation, Uploaded: test.txt, and Test file uploaded successfully!.
Upload failed or file not found?
- Make sure test.txt is saved in the same folder as s3_backup.py (the s3-backup-tool folder).
- Check that your terminal is running from inside the s3-backup-tool folder. Run ls to confirm you can see both files.
Still stuck? Help me debug my S3 file upload error
✔️ Awesome, I've got everything!
Great. Double check you've saved s3_backup.py and confirm the output matches what you expected.
ⓧ I'd like to double check the full code
Your s3_backup.py should contain these sections, in order. Scroll up through this step to confirm each one is present:
- Imports: boto3, os, sys, datetime, and ClientError.
- Constants: BUCKET_NAME, BACKUP_FOLDER, and REGION.
- Function: create_s3_client().
- Function: list_buckets().
- Function: create_bucket().
- Function: upload_file().
- Main block: creates client, prints connection message, lists buckets, creates bucket, uploads test.txt, and prints success.
Your Python code just created a real S3 bucket in the cloud and uploaded a file into it. Next up, you'll build a batch uploader that handles an entire folder of business files at once.
Build the Batch File Uploader
In Step 2 you proved your Python code can talk to AWS by creating a bucket and uploading a single test file. That's a great start, but a real backup system doesn't upload one file at a time.
In this step, you'll teach your script to scan an entire folder of business documents and upload every file automatically. You'll also list what landed in your bucket to confirm the backup, then discover a real S3 constraint when you try to clean up.
In this step, get ready to:
- Upload an entire folder of business files to S3 in one run.
- List every object in your bucket with its name, size, and timestamp.
- Discover why deleting a non-empty bucket fails.
Create your sample business files
Your backup script needs something to back up. You'll create a folder of realistic business documents that the script will upload to S3.
- Create a business_files folder inside your s3-backup-tool project by running this command:
mkdir business_files
- In your code editor, create a new file called quarterly_report.txt inside the business_files folder.
- Paste in the following sample content:
Q2 2026 Revenue: $1.2M
Q2 2026 Expenses: $890K
Net Profit: $310K
Growth: 12% year-over-year
- Create a second file called employee_list.csv inside business_files with this content:
name,department,start_date
Alice Johnson,Engineering,2024-01-15
Bob Smith,Marketing,2023-06-01
Carol Williams,Finance,2025-03-10
- Create a third file called meeting_notes.txt inside business_files with this content:
Team standup - August 21 2026
Discussed Q3 roadmap priorities
Action items: finalize budget proposal by Friday
Next meeting: Monday 9am
- Save all three files.
- Confirm the files exist by running:
ls business_files
You should see employee_list.csv, meeting_notes.txt, and quarterly_report.txt listed.
Don't see your files?
Make sure you created the files inside the business_files folder, not in the project root. Check that the folder name matches exactly.
help me find where my business files ended up
Write the upload_folder() function
Right now your script uploads one file at a time using upload_file(). You need a function that loops through every file in a folder and uploads them all.
- In s3_backup.py, find the upload_file() function.
- Add the following upload_folder() function below it:
def upload_folder(s3_client, folder_path, bucket_name):
"""Upload all files in a folder to S3."""
uploaded_files = []
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
if os.path.isfile(file_path):
upload_file(s3_client, file_path, bucket_name)
uploaded_files.append(filename)
return uploaded_files
What does this code do?
- os.listdir(folder_path) returns a list of every file and folder name inside business_files/.
- os.path.join() combines the folder path and filename into a full path like business_files/quarterly_report.txt.
- os.path.isfile() makes sure you only upload files, not subdirectories.
- uploaded_files.append(filename) tracks which files were uploaded so you can report on them later.
Now update the main block to use this new function instead of the single-file upload.
- Scroll down to the if __name__ == "__main__": block at the bottom of your file.
- Find these two lines:
upload_file(s3, 'test.txt', BUCKET_NAME)
print("Test file uploaded successfully!")
- Replace those two lines with the following:
print(f"\nUploading files from '{BACKUP_FOLDER}/':")
uploaded = upload_folder(s3, BACKUP_FOLDER, BUCKET_NAME)
print(f"\n{len(uploaded)} file(s) uploaded successfully!")
- Save s3_backup.py.
- Run the script by running:
python s3_backup.py
You should see your script connect to AWS, create a new bucket, then upload each file from business_files/ one by one. The output ends with a count like 3 file(s) uploaded successfully!.
Seeing a FileNotFoundError?
Make sure your business_files folder is inside the s3-backup-tool project folder, and that your terminal is in s3-backup-tool when you run the script.
help me fix a FileNotFoundError in my S3 backup script
List bucket contents and attempt cleanup
Your files are in the cloud, but you have no way to verify that from within the script. You need a function that lists every object in your bucket with useful details like size and timestamp.
- In s3_backup.py, add the following list_objects() function below upload_folder():
def list_objects(s3_client, bucket_name):
"""List all objects in a bucket with details."""
response = s3_client.list_objects_v2(Bucket=bucket_name)
if "Contents" not in response:
print("\nBucket is empty.")
return []
objects = response["Contents"]
print(f"\nBucket '{bucket_name}' contains {len(objects)} file(s):")
for obj in objects:
size_kb = obj["Size"] / 1024
print(f" - {obj['Key']} ({size_kb:.1f} KB, modified: {obj['LastModified']})")
return objects
What does this code do?
- list_objects_v2() asks S3 for the contents of your bucket. The response is a dictionary.
- The Contents key only exists when the bucket has objects. The if check prevents a crash on an empty bucket.
- Each object in Contents is a dictionary with Key (filename), Size (bytes), and LastModified (timestamp).
- obj["Size"] / 1024 converts bytes to kilobytes so the sizes are easier to read.
Now wire this function into the main block, and add a line to try deleting the bucket at the very end.
- In the if __name__ == "__main__": block, find this line:
print(f"\n{len(uploaded)} file(s) uploaded successfully!")
- Add the following lines below it:
list_objects(s3, BUCKET_NAME)
s3.delete_bucket(Bucket=BUCKET_NAME)
Why delete the bucket at the end?
A production backup tool should clean up after itself to avoid ongoing storage costs. This line attempts to delete the bucket after listing its contents.
- Save s3_backup.py.
Before you run this, think about what will happen. You just uploaded multiple files into the bucket, and now the script tries to delete it. Do you think the delete will succeed?
- Run your script:
python s3_backup.py
You'll see each file upload successfully and a listing of all objects with their sizes and timestamps. Then the script crashes with an error that includes BucketNotEmpty.
This error is expected
S3 requires you to delete every object inside a bucket before the bucket itself can be deleted. Your script tried to delete a bucket that still has files in it, so AWS rejected the request.
This is a real constraint you'll hit in production AWS automation. In the next step, you'll write proper cleanup code that empties the bucket first, then deletes it.
✔️ Awesome, I've got everything!
Great work. Double check you've saved s3_backup.py and that your terminal shows the BucketNotEmpty error at the end.
ⓧ I'd like to double check the full code
Scroll through your s3_backup.py file and confirm these sections are present, in order:
- Imports: boto3, os, sys, datetime, ClientError.
- Constants: BUCKET_NAME, BACKUP_FOLDER, REGION.
- Functions: create_s3_client(), list_buckets(), create_bucket(), upload_file(), upload_folder(), list_objects().
- Main block: connects to S3, lists buckets, creates a bucket, uploads the folder, lists objects, then calls s3.delete_bucket() at the end.
Script runs but uploads zero files?
Make sure your business_files folder is in the same directory where you run python s3_backup.py. The BACKUP_FOLDER constant is set to "business_files", which is a relative path.
help me debug why my upload_folder function isn't finding any files
Your script can now upload an entire folder and list everything in the bucket. But it can't clean up after itself because S3 won't let you delete a non-empty bucket. Next up, you'll fix that by writing a proper cleanup function and adding a professional backup report.
Generate the Backup Report and Clean Up
Your batch uploader works. You can scan a folder and push every file to S3 in one go. But there is an unresolved gap: when you tried to delete your bucket at the end of Step 3, AWS responded with a BucketNotEmpty error.
S3 requires all objects to be deleted before the bucket itself can be removed. In this step, you will fix that by building a proper cleanup function. You will also add a professional backup report that documents exactly what was backed up, with file sizes and timestamps.
In this step, get ready to:
- Write a report generator that documents every backed-up file with its size and timestamp.
- Build a cleanup function that empties and deletes the S3 bucket in one sweep.
- Wire everything into a complete end-to-end script and run it.
Write the backup report generator
A backup without a report is a backup nobody trusts. Your generate_report() function will query the bucket, calculate totals, and produce a formatted summary that gets printed to screen and saved to a local file.
- In s3_backup.py, add the following generate_report() function below your list_objects() function:
def generate_report(s3_client, bucket_name):
"""Generate a backup report and save it to a file."""
response = s3_client.list_objects_v2(Bucket=bucket_name)
if "Contents" not in response:
print("No objects to report.")
return
objects = response["Contents"]
total_size = sum(obj["Size"] for obj in objects)
report_lines = []
report_lines.append("=" * 55)
report_lines.append(" S3 BACKUP REPORT")
report_lines.append("=" * 55)
report_lines.append(f"Bucket: {bucket_name}")
report_lines.append(f"Report Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
report_lines.append(f"Total Files: {len(objects)}")
report_lines.append(f"Total Size: {total_size / 1024:.1f} KB")
report_lines.append("-" * 55)
report_lines.append(f"{'File Name':<30} {'Size (KB)':<12} {'Last Modified'}")
report_lines.append("-" * 55)
for obj in objects:
size_kb = obj["Size"] / 1024
modified = obj["LastModified"].strftime("%Y-%m-%d %H:%M")
report_lines.append(f"{obj['Key']:<30} {size_kb:<12.1f} {modified}")
report_lines.append("=" * 55)
report_text = "\n".join(report_lines)
print("\n" + report_text)
with open("backup_report.txt", "w") as f:
f.write(report_text)
print("\nReport saved to backup_report.txt")
What does this code do?
- list_objects_v2() fetches every object in the bucket. If Contents is missing from the response, the bucket is empty and the function exits early.
- sum() with a generator expression calculates the total byte count across all files in one line.
- The :<30 and :<12 format specifiers left-align each column so the report lines up neatly in the terminal.
- open("backup_report.txt", "w") writes the same report string to a local file so you have a permanent record even after the bucket is deleted.
Now let's hook the report into your script so you can see it in action.
- Scroll down to the if __name__ == "__main__": block at the bottom of s3_backup.py.
- Find this line:
s3.delete_bucket(Bucket=BUCKET_NAME)
- Replace that line with the following two lines:
print("\nGenerating backup report...")
generate_report(s3, BUCKET_NAME)
- Save s3_backup.py.
- Run your script by running this command:
python s3_backup.py
You should see your script connect to S3, create a new bucket, upload your business files, list them, and then print a formatted backup report to the terminal. You should also see "Report saved to backup_report.txt" at the end.
- Confirm backup_report.txt was created by running:
ls
You should see backup_report.txt listed alongside s3_backup.py and your other project files.
Not seeing the report?
- Check that generate_report() is defined above the if __name__ block, not inside it. Python needs to see the function definition before the call.
- Make sure you removed the old s3.delete_bucket(Bucket=BUCKET_NAME) line entirely, not just added lines above it. If the old delete line is still there, you will see the BucketNotEmpty error again.
- If the report shows no files, double check that your business_files/ folder still contains files from Step 3.
help me debug my generate_report function
What about the bucket from this run?
Your script created a new bucket and uploaded files, but it no longer tries to delete the bucket. That bucket is sitting in AWS right now. The cleanup function you build next will handle bucket deletion properly. The leftover bucket costs fractions of a cent in storage.
Fix the cleanup and run end-to-end
Remember the BucketNotEmpty error from Step 3? That happened because S3 requires every object to be removed before the bucket itself can be deleted. Your cleanup_bucket() function will handle both steps: delete all objects first, then delete the empty bucket.
- In s3_backup.py, add the following cleanup_bucket() function below your generate_report() function:
def cleanup_bucket(s3_client, bucket_name):
"""Empty and delete a bucket."""
response = s3_client.list_objects_v2(Bucket=bucket_name)
if "Contents" in response:
objects_to_delete = [{"Key": obj["Key"]} for obj in response["Contents"]]
s3_client.delete_objects(
Bucket=bucket_name,
Delete={"Objects": objects_to_delete}
)
print(f"\nDeleted {len(objects_to_delete)} object(s) from '{bucket_name}'.")
s3_client.delete_bucket(Bucket=bucket_name)
print(f"Bucket '{bucket_name}' deleted successfully!")
What does this code do?
- The function first calls list_objects_v2() to find every object in the bucket.
- A list comprehension builds a list of {"Key": obj["Key"]} dictionaries, which is the exact format delete_objects() expects.
- delete_objects() removes up to 1,000 objects in a single API call. For your 3-4 business files, one call is more than enough.
- Only after all objects are gone does the function call delete_bucket(). This is the proper two-step cleanup that avoids the BucketNotEmpty error.
Now wire the cleanup into your main block so the script runs start-to-finish.
- Scroll to the if __name__ == "__main__": block at the bottom of s3_backup.py.
- Find this line (the one you added in the previous substep):
generate_report(s3, BUCKET_NAME)
- Add these lines directly below it:
print("\nCleaning up AWS resources...")
cleanup_bucket(s3, BUCKET_NAME)
print("\nAll done! Backup report saved locally. AWS resources cleaned up.")
- Save s3_backup.py.
Before you run this, think about what you expect to happen. In Step 3, deleting the bucket failed because it was not empty. This time, your script empties the bucket first. Do you think the delete will succeed?
- Run the full script by running this command:
python s3_backup.py
This time, after the backup report prints, you should see messages confirming the cleanup: "Deleted X object(s)" followed by "Bucket deleted successfully!" and then "All done!" at the very end.
Seeing an error during cleanup?
- If you see NoSuchBucket, the bucket name in your BUCKET_NAME constant uses a timestamp that changes every run. Each execution creates a fresh bucket. Make sure you have not changed the BUCKET_NAME line at the top of your file.
- If you see BucketNotEmpty again, double check that cleanup_bucket() calls delete_objects() before delete_bucket(). The order matters.
- If you see a NameError for cleanup_bucket, make sure the function is defined above the if __name__ block, not inside it.
help me debug the cleanup in my S3 backup script
Clean up leftover buckets from earlier runs
Each time your script runs, it creates a new bucket with a fresh timestamp. The runs from Step 3 and from the report test earlier in this step left behind buckets that were never cleaned up. To delete them, open the S3 console in AWS, find any buckets starting with my-business-backup-, empty each one, and then delete it.
These leftover buckets cost fractions of a cent in storage, so there is no urgency. But cleaning them up is good practice.
✔️ Awesome, I've got everything!
Great. Double check you have saved s3_backup.py and that backup_report.txt is sitting in your s3-backup-tool folder.
ⓧ I'd like to double check the full code
Your s3_backup.py file is too long to show in one block, so scroll through your file and confirm these sections appear in this order:
- Imports: boto3, os, sys, datetime, ClientError.
- Constants: BUCKET_NAME, BACKUP_FOLDER, REGION.
- Functions in this order: create_s3_client(), list_buckets(), create_bucket(), upload_file(), upload_folder(), list_objects(), generate_report(), cleanup_bucket().
- The if __name__ == "__main__": block at the bottom calls create_s3_client(), list_buckets(), create_bucket(), upload_folder(), list_objects(), generate_report(), and cleanup_bucket() in that order, ending with the "All done!" print.
Your backup automation script is complete. It connects to AWS, creates a bucket, uploads a folder of files, generates a professional report, and properly cleans up every cloud resource when it finishes. That is a real-world automation pattern you will see in production environments.
Secret mission
Add Duplicate Detection
Your backup script uploads every file, every time. A real backup system would check what's already in the cloud and skip duplicates. Can you make your script smart enough to know the difference?
Clean Up Your Resources
Clean Up Your Resources
This project uses a paid AWS account. Your script's cleanup_bucket() function already deletes your S3 bucket each time it runs, so if your last run completed successfully, there are no cloud resources left to clean up.
Cost warning
If your script errored before the cleanup step ran, you may have a lingering S3 bucket in your AWS account. Even small files stored in S3 Standard incur ongoing charges (fractions of a cent per hour for small files). Follow the Delete tab below to remove it.
Decide whether to keep your local project files and tools, or delete them entirely.
Resources you used:
- S3 bucket (auto-deleted by your script, but may linger if the script errored before cleanup).
- Local project folder s3-backup-tool on your Desktop (contains s3_backup.py, business_files/, test.txt, and backup_report.txt).
- boto3 Python package (installed via pip).
- AWS CLI v2 (installed in Step 1).
- AWS credentials (stored in your .aws folder by aws configure).
Keep everything running
No action needed. Choose this if you plan to keep building with Python and AWS.
- Your local project files stay in the s3-backup-tool folder on your Desktop.
- boto3 and the AWS CLI remain installed for future projects.
- Your AWS credentials remain configured in the .aws folder in your home directory.
- If your script ran to completion, there are no cloud resources left. Nothing is costing you money.
Pause - I'll come back to this later
S3 has no pause state. If your script completed its cleanup step, there are no cloud resources running.
- Your local project files and tools stay as they are.
- If your script errored before cleanup ran, follow the Delete tab to remove the lingering S3 bucket. You can keep everything else.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below.
Delete any lingering S3 bucket
If your script errored before the cleanup_bucket() function ran, your bucket may still exist in AWS.
- Log into the AWS S3 console at https://s3.console.aws.amazon.com/s3/buckets.
- Look for a bucket starting with my-business-backup-.
- If you find one, select it and click Empty. Confirm by typing permanently delete in the confirmation field.
- After emptying, select the bucket again and click Delete. Confirm by typing the bucket name.
Delete local project files
- Delete the project folder by running this command:
macOS
rm -rf ~/Desktop/s3-backup-tool
Windows
Remove-Item -Recurse -Force ~\Desktop\s3-backup-tool
Uninstall boto3
- Remove the boto3 package by running:
pip uninstall boto3
Uninstall the AWS CLI
- Open Control Panel, then select Programs and Features.
- Select AWS Command Line Interface and click Uninstall.
- Confirm that you want to uninstall the AWS CLI.
Remove AWS credentials
The aws configure command stored your access keys in a .aws folder in your home directory. Only delete this folder if you do not use any other AWS tools or SDKs on this machine.
macOS
rm -rf ~/.aws
Windows
Remove-Item -Recurse -Force $env:USERPROFILE\.aws
Nice Work!
Nice Work!
Nice work! You just built a Python-powered backup automation tool that connects to AWS, uploads an entire folder of business files to S3, generates a professional report, and cleans up all cloud resources when it finishes.
You've learned how to:
- Connected Python to AWS using boto3 and the AWS CLI to create, manage, and delete S3 buckets programmatically.
- Built a batch file uploader that scans a local folder and uploads every file to Amazon S3, then lists each object with its size and timestamp.
- Generated a professional backup report with file metadata and handled a real AWS error (BucketNotEmpty) by writing a cleanup function that empties and deletes buckets safely.
- Secret Mission: Added duplicate detection so your backup script skips files that already exist in S3 instead of re-uploading them.
Ready to quiz yourself?