3 min read

Automated S3 Backup Tool

This guide demonstrates how to automate file backups to Amazon S3 using the boto3 library. It supports backing up individual files or recursively uploading entire directories while preserving the folder structure.

Modules Used:

  • boto3: The Amazon Web Services (AWS) SDK for Python.
  • botocore: Used for handling AWS-specific exceptions.
  • argparse: To handle command-line arguments.
  • pathlib: For robust file path handling.

Prerequisites

  1. AWS Account: You need an active AWS account.
  2. S3 Bucket: Create a bucket in S3 (e.g., my-backup-bucket).
  3. IAM User: Create an IAM user with AmazonS3FullAccess (or specific write permissions).
  4. Configuration: Configure your credentials locally.
    • Install AWS CLI: pip install awscli
    • Run: aws configure (Enter Key ID, Secret Key, Region).

Installation

pip install boto3

The Code

Save this as s3_backup.py.

import boto3
from botocore.exceptions import ClientError
import argparse
import os
from pathlib import Path
import sys

def upload_file(s3_client, file_name, bucket, object_name):
    """Uploads a single file to S3."""
    try:
        s3_client.upload_file(file_name, bucket, object_name)
        print(f"Uploaded: {file_name} -> s3://{bucket}/{object_name}")
        return True
    except ClientError as e:
        print(f"Error uploading {file_name}: {e}")
        return False

def backup_to_s3(source, bucket, prefix=""):
    source_path = Path(source).resolve()

    if not source_path.exists():
        print(f"Error: Source '{source}' not found.")
        return

    # Initialize S3 client once
    s3 = boto3.client('s3')

    files_to_upload = []

    if source_path.is_file():
        # Single file backup
        # Use the filename as the object key, optionally prepended with a prefix
        s3_key = os.path.join(prefix, source_path.name).replace("\\", "/")
        files_to_upload.append((str(source_path), s3_key))
    else:
        # Directory backup (Recursive)
        print(f"Scanning '{source}'...")
        for file_path in source_path.rglob("*"):
            if file_path.is_file():
                # Calculate relative path to maintain folder structure in S3
                # e.g., /home/user/data/img.png -> data/img.png
                rel_path = file_path.relative_to(source_path.parent)
                s3_key = os.path.join(prefix, rel_path).replace("\\", "/")
                files_to_upload.append((str(file_path), s3_key))

    print(f"Found {len(files_to_upload)} files to upload to bucket '{bucket}'.")

    success_count = 0
    for local_file, s3_key in files_to_upload:
        if upload_file(s3, local_file, bucket, s3_key):
            success_count += 1

    print(f"\nBackup complete. Uploaded {success_count}/{len(files_to_upload)} files.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="S3 Backup Utility")
    parser.add_argument("source", help="Local file or directory to backup")
    parser.add_argument("bucket", help="Target S3 Bucket name")
    parser.add_argument("--prefix", default="", help="S3 Folder prefix (optional)")

    args = parser.parse_args()

    backup_to_s3(args.source, args.bucket, args.prefix)

Usage

# Backup a single file
python s3_backup.py important_doc.pdf my-backup-bucket

# Backup a directory to a specific folder in S3
python s3_backup.py ./projects my-backup-bucket --prefix backups/2023

programming/python/python