Create an S3 Bucket

Create an S3 bucket and upload objects — AWS Cloud & DevOps with Python.

Focus: create an s3 bucket and upload objects

Sponsored

Storing files on a server's local disk works until your app grows, your team needs shared access, or your data must survive a data center failure. Manually wiring up network storage, managing permissions, and building upload endpoints is a huge time sink that distracts from your actual product. Amazon S3 (Simple Storage Service) solves this by giving you a highly durable, scalable, and secure object storage service over a simple API — and you can control it completely with Python, making it a cornerstone of any AWS DevOps workflow.

The problem this lesson solves

When your Python application needs to store user uploads, logs, backups, or static assets, you need a storage solution that is reliable, scalable, and easy to access from anywhere. Storing files on a single EC2 instance's disk is fragile: the data disappears if the instance terminates, it's not shared across instances, and it doesn't scale beyond that machine's disk limit. Setting up your own file server with NFS or a database BLOB column adds operational overhead and becomes a bottleneck.

S3 removes these headaches by providing a fully managed object storage service. You create a bucket (a container for objects) and upload objects (files) to it. S3 handles durability by replicating data across multiple Availability Zones, scales seamlessly to exabytes, and offers fine-grained access control. For a DevOps engineer, S3 is not just a place to dump files — it's a building block for static website hosting, data lakes, backup pipelines, and infrastructure state storage (like Terraform state files).

Core concept / mental model

Think of S3 as a giant, global filing cabinet with unlimited drawers. Each drawer is a bucket, and the files you place inside are objects. Every bucket must have a globally unique name, just like a domain name. Inside a bucket, files are organized by keys, which are essentially the full path to the object (e.g., images/profile.png).

Here's the key mental model:

  • Bucket = a container for objects. You control its name, region, and access policies.
  • Object = a file plus metadata. Each object can be up to 5 TB in size.
  • Key = the unique identifier of an object within a bucket. It's the full path, including slashes.
  • Region = the physical data center location where your bucket lives. Choosing the right region affects latency and cost.
  • Availability = S3 automatically replicates objects across multiple facilities within a region, guaranteeing 99.999999999% durability.

This model is simple but powerful. You don't manage servers or disks; you just interact with a flat namespace via the S3 API. The boto3 library is the official AWS SDK for Python, and it mirrors this model beautifully: you create a client or resource, then call methods to create buckets and upload files.

How it works step by step

Let's trace the lifecycle of an S3 bucket and an object upload from your Python code to S3:

  1. Authenticate with AWS. You need valid AWS credentials. Typically, these are an Access Key ID and Secret Access Key from an IAM user. In code, you can pass them explicitly, set environment variables, or use an IAM role (on EC2 or Lambda). The SDK will use the default credential chain if you don't specify them.

  2. Create a client or resource. boto3 gives you two APIs: the low-level client (closer to the REST API) and the high-level resource (more Pythonic). Both are correct; choose based on your preference and needs.

  3. Create the bucket. You call create_bucket() with a unique name and a region. For most regions, you must specify CreateBucketConfiguration with LocationConstraint. If you skip it, it defaults to us-east-1.

  4. Upload an object. You use put_object() or upload_file() to send data. put_object() takes a bytes or file-like object; upload_file() takes a local file path and handles multipart uploads for large files automatically.

  5. Verify. Optionally, you can list objects in the bucket or fetch the object's metadata to confirm the upload succeeded.

This flow is request/response — each call goes over HTTPS to the S3 endpoint, processes server-side, and returns a response. The SDK handles retries, timeouts, and serialization for you.

Hands-on walkthrough

Let's get our hands dirty. First, ensure you have boto3 installed and your AWS credentials configured.

pip install boto3
aws configure   # sets AWS Access Key ID, Secret, region, and output format

Creating a bucket

The following script creates a bucket in the us-west-2 region. Note that the bucket name must be globally unique — if you get an error, add a random suffix.

import boto3

# Create an S3 client
s3_client = boto3.client('s3', region_name='us-west-2')

bucket_name = 'my-devops-python-bucket-2025'

# Create the bucket
try:
    s3_client.create_bucket(
        Bucket=bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'us-west-2'}
    )
    print(f'Bucket {bucket_name} created successfully.')
except Exception as e:
    print(f'Error creating bucket: {e}')

Expected output (if successful):

Bucket my-devops-python-bucket-2025 created successfully.

Uploading an object

Now let's upload a local file to that bucket. We'll use both upload_file (for local files) and put_object (for strings/bytes).

import boto3

s3_client = boto3.client('s3', region_name='us-west-2')
bucket_name = 'my-devops-python-bucket-2025'

# Upload a local file
s3_client.upload_file(
    Filename='local_report.txt',
    Bucket=bucket_name,
    Key='reports/report-2025.txt'  # the object key, can include 'folders'
)
print('Local file uploaded as reports/report-2025.txt')

# Upload a string as an object
s3_client.put_object(
    Bucket=bucket_name,
    Key='config/settings.json',
    Body='{"environment": "production", "debug": false}'
)
print('JSON string uploaded as config/settings.json')

Expected output:

Local file uploaded as reports/report-2025.txt
JSON string uploaded as config/settings.json

You can verify the uploads by listing objects:

response = s3_client.list_objects_v2(Bucket=bucket_name)
if 'Contents' in response:
    for obj in response['Contents']:
        print(obj['Key'], obj['Size'], obj['LastModified'])
else:
    print('Bucket is empty.')

Expected output (similar):

config/settings.json 37 2025-01-01 12:00:00+00:00
reports/report-2025.txt 123 2025-01-01 12:00:01+00:00

Pro tip: Use upload_file() for files larger than a few MB because it automatically switches to multipart upload, which is faster and more reliable.

Compare options / when to choose what

There are multiple ways to upload objects to S3, and the choice depends on your use case.

Method Best for Pros Cons
put_object Small objects (strings, bytes) Simple, one call Limited to 5 GB, no automatic retry for large data
upload_file Medium to large local files Handles multipart automatically, progress callbacks Requires local file path
upload_fileobj Streaming or file-like objects Works with non-seekable streams, memory-efficient Slightly more complex
AWS CLI Manual ops / scripting No code needed, easy Not programmatic inside Python
Console / GUI One-off uploads Visual, no code Not automated

For most DevOps tasks, upload_file is the sweet spot — it handles scaling and retries for you. If you're working with an in-memory buffer or a generator, use upload_fileobj. For configuration files and small JSON blobs, put_object is perfectly fine.

Variations

  • boto3 resource vs. client: The resource API is more object-oriented, but the client gives you access to every S3 feature. For advanced features (like bucket policies), stick with the client.
  • Using AWS CLI: You can create buckets and upload objects with aws s3api create-bucket and aws s3 cp. It's great for scripting but less flexible than Python.
  • Server-side encryption: You can encrypt objects at rest by specifying ServerSideEncryption='AES256' or using KMS. This is critical for sensitive data.

Troubleshooting & edge cases

Bucket name already exists

Error: Botocore ClientError: An error occurred (BucketAlreadyExists) when calling the CreateBucket operation: The requested bucket name is not available.

Cause: S3 bucket names are globally unique across all AWS accounts.

Fix: Choose a more unique name (e.g., add your company name and a random string).

Permission denied

Error: AccessDenied when creating bucket or uploading.

Cause: Your IAM user lacks s3:CreateBucket, s3:PutObject, or s3:ListBucket permissions.

Fix: Attach a policy like AmazonS3FullAccess (or a custom policy) to your IAM user.

Region mismatch

Error: IllegalLocationConstraintException when creating a bucket.

Cause: You specified a LocationConstraint that doesn't match the client's region, or you didn't specify it for a non-default region.

Fix: Always set region_name in boto3.client() to the same region as your LocationConstraint.

Slow uploads for large files

Symptom: Uploading a 1 GB file takes forever or times out.

Fix: Use upload_file() which automatically uses multipart upload. You can also tune Config(transfer_config) with max_concurrency and multipart_threshold.

Edge case: Empty objects

Uploading an empty string or empty file works fine, but be aware that S3 objects are immutable — you can't append data. To change an object, you upload a new version (if versioning is enabled) or overwrite it.

What you learned & what's next

You now understand the core principles of creating an S3 bucket and uploading objects with Python. You learned the mental model of buckets and objects, the step-by-step API flow, and how to compare different upload methods. You also know how to troubleshoot common errors like bucket name collisions and permission issues.

Next up in the track, you'll likely dive into more advanced S3 features like versioning, lifecycle policies, and static website hosting. You might also connect S3 to other AWS services (e.g., Lambda triggers for image processing). Those lessons will build directly on the skills you just practiced.

Pro tip: Always follow the principle of least privilege. Grant your IAM users only the S3 permissions they need (e.g., s3:PutObject on a specific bucket), not full S3 access.

Now, go create a bucket, upload an object, and then try to list it using list_objects_v2. You'll be surprised how easy it is to build a robust storage layer for your Python apps.

Practice recap

Create a new bucket with a unique name, upload a sample text file using upload_file, then list all objects to confirm. Try uploading an in-memory JSON string with put_object and inspect the metadata. Finally, attempt to create the same bucket again to see the BucketAlreadyExists error, and adjust your script to handle it gracefully.

Common mistakes

  • Forgetting to specify CreateBucketConfiguration when creating a bucket outside us-east-1 — causes IllegalLocationConstraintException.
  • Using a bucket name that isn't globally unique — get BucketAlreadyExists error. Always add a random suffix.
  • Hardcoding AWS credentials in your code instead of using environment variables or IAM roles — a security risk.
  • Using put_object for very large files — hits the 5 GB limit and lacks automatic multipart handling; use upload_file.

Variations

  1. Use boto3.resource('s3') and Bucket.put_object() for a more Pythonic, object-focused API.
  2. Use AWS CLI: aws s3api create-bucket and aws s3 cp for quick manual operations.
  3. Enable server-side encryption with ServerSideEncryption='AES256' or KMS for sensitive data.

Real-world use cases

  • Store and serve user-generated content like profile pictures, videos, or documents in a web app.
  • Automate backup of databases and logs to S3 using a Python script with upload_file.
  • Host static assets (CSS, JS, images) for a website, with S3 as an origin for CloudFront CDN.

Key takeaways

  • S3 stores data as objects in globally unique buckets, with keys acting as paths.
  • Use boto3.client('s3') and create_bucket() with location constraint for correct region deployment.
  • Choose upload_file for local files and put_object for in-memory strings; both are simple and reliable.
  • Verify uploads with list_objects_v2 to confirm the object exists and its size/metadata.
  • Always handle bucket name collisions and region mismatches to avoid common errors.
  • Next, explore versioning and lifecycle policies to manage object retention and costs.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.