Implementing Storage with Amazon S3

Adding cloud storage to your web application is a key milestone in building something scalable, robust, and production-ready. In our image-sharing app, we want users to upload pictures and retrieve them from anywhere in the world. To do this reliably, we’ll set up Amazon S3 as our backend for storing media files.
This guide walks through every step of that setup using the exact deployment flow from our app—giving you a real-world learning experience you can apply right away.
Why use Amazon S3?
In the early stages of building a web application, it's common to store uploaded files—like images, documents, or videos—on the same server that runs the app. While this approach works during development, it introduces significant limitations as the application grows.
When files are stored locally:
- Data isn't persistent across deployments: Restarting or replacing a server (or container) can result in data loss if uploads aren't backed up.
- Scaling becomes difficult: If you add more servers to handle increased traffic, there's no shared access to files unless you manually sync them across each server.
- Serving content globally is inefficient: Local storage relies on a single server, which may be geographically far from your users, resulting in slower load times.
Amazon S3 addresses all of these challenges. It offers a dedicated storage service that is:
- Durable: Your data is automatically stored across multiple facilities for redundancy.
- Scalable: You can store virtually unlimited files without changing your infrastructure.
- Globally accessible: Users can retrieve content from anywhere using reliable URLs.
- Seamlessly integrated: Works well with other AWS services and deployment workflows.
By decoupling file storage from your web server (such as EC2), you make your architecture more modular, fault-tolerant, and ready for production. Let’s see how to set up S3 for our app.
Setting Up Amazon S3 for Media Storage
Now that you understand why Amazon S3 is important for handling static content, let’s move into the setup process. The following steps reflect the current AWS interface and follow the structure used in your image sharing app deployment.
Step 1: Create an S3 bucket
Now we’ll create the storage container that will hold your media files.
1. Go to the AWS Console and open the S3 service.

2. Click Create bucket.

General configuration
- Bucket type: General Purpose
- Bucket name: Use something globally unique, e.g.,
image-sharing-app-media
.

Object Ownership
- Select ACLs disabled (recommended)

Block Public Access settings for this bucket
- Uncheck all boxes to allow public file access
- Acknowledge the warning about making the bucket public

Keep other settings as they are. Then, click Create bucket.

Step 2: Add a bucket policy (S3)
Now we’ll give read access to the files in your bucket so users can view uploaded images.
- Go to your newly created bucket.
- Click on the Permissions tab.
- Scroll to Bucket policy and click Edit.

Paste in the following JSON, replacing the bucket name:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::image-sharing-app-media/*"
}
]
}
Click Save changes.

Step 3: Create an IAM user with S3 access
To allow your app to upload images to the bucket, you’ll need an IAM user with the proper permissions.
Open the IAM service in AWS.

Create a user for S3 access
Go to Users, then click Create user.

Name the user something like image-sharing-app-s3
, and click Next.
To set permission, Choose Attach policies directly and Search for and select AmazonS3FullAccess.

Click Next to finish creating the user.
Generate programmatic credentials
Go to the user page you created just now.

On the user’s detail page, go to the Security credentials tab. Scroll to the Access keys section and click Create access key.

When prompted for the use case, select “Application running on an AWS compute service”.

Click Next.
And Create access key.

Copy or download the Access Key ID and Secret Access Key now—you won't see the secret key again.

Step 4: Update .env.prod for S3
Open .env.prod
and add or update the following variables:
USE_S3=True
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_STORAGE_BUCKET_NAME=image-sharing-app-media
AWS_S3_REGION_NAME=your_aws_region (e.g., us-east-1)
This switches your storage backend to use Amazon S3.
Step 5: Restart your app with new configuration
To apply the new environment settings, restart your Docker containers:
docker-compose -f docker-compose.prod.yml --env-file .env.prod down
docker-compose -f docker-compose.prod.yml --env-file .env.prod up -d
This rebuilds your containers with S3 as the active storage backend.
Step 7: Upload image files
First, you’ll see that the posted images on the home page are not displayed. This is because the image file paths are now pointing to the S3 storage.

Let’s demonstrate how S3 works.
Image upload via the app
First, upload an avatar image. You can use the avatar image from our test data.

Go to the S3 Console, select your bucket, and check if the image has appeared

Image upload directly to S3 storage
Currently, image post data is in the database but image files are not uploaded to S3. To handle bulk image upload, you can directly upload files to S3.
Open the images folder (if it doesn’t exist create it but make sure using exactly the same folder name not like image) in S3 and simply drag and drop the image files to S3.

Then, click the upload button.

You’ll see the images in the home page are recovered.

Wrapping up
At this point, you’ve connected your application to Amazon S3—a major step in making your project more scalable and production-ready. Your media files are now stored in a reliable, cloud-based system that can handle uploads from anywhere, at any time.
With storage in place, it’s time to turn our attention to the rest of the infrastructure. In the next chapter, we’ll focus on shaping a full cloud environment around your app. You’ll define your network using a Virtual Private Cloud (VPC), rebuild your EC2 instance using AMIs, and connect your application to a managed database with Amazon RDS. From there, we’ll scale with Elastic Load Balancing and keep an eye on performance using CloudWatch.
Let’s move forward and start building out the pieces that will take your app from functional to fully cloud-native.