Implementing Amazon S3 Buckets for your Django Website

It is a good idea and sometimes even necessary to store your media files outside of your server. In the case of an app being implemented on Heroku, then you need to use an alternative media storage such as Amazon S3 if you're providing features where users can uploads images or other types of media.

Amazon's S3 has a number of steps to set up for your website which we'll run through here.

You will need a couple of additional packages installing within your project. I personally use Docker for all of my development and production implementation, so I'll go through the steps using Docker. If you're using a Python virtual environment and package manager like pipenv, the steps are exactly the same except that you will need to remove the docker component that is prepended to the commands.

Assuming that your Docker setup uses a service called 'web' for your Django project, install relevant project packages:

$ docker-compose exec web pipenv install boto3
$ docker-compose exec web pipenv install django-storages

Next up, you will need to configure your settings in your Django project. Make the region the one that is where the majority of your website visitors are likely to be located.

# Settings.py

...

INSTALLED_APPS = [
    ...          # List of pre-installed Django apps

    # Third Party
    ...          # List of 3rd party packages you installed for your Django project
    'storages',  # New

    # Project Apps
    ...          # List of project apps you built for your Django project
]

...

# Django Storages Settings
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_STORAGE_BUCKET_NAME = os.environ['AWS_STORAGE_BUCKET_NAME']
AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID']
AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY']
AWS_S3_FILE_OVERWRITE = False
AWS_S3_REGION_NAME = 'eu-west-2'
AWS_DEFAULT_ACL = None

 

Log in to your AWS Management Console and do a search for IAM.

Although this tutorial is not about setting up the Identity and Access Management for your Amazon account, it is recommended that you do so. In short, you will need to set up a group with some policy permissions to handle whatever aspects of Amazon's Web Services that you wish to use. For now, create a group and then attach the policy called 'AmazonS3FullAccess' to the group.

You will then need to create a user and attach that user to the group. This gives this user (namely you) full access to Amazon's S3 file storage web service. If you do not use Identity and Access Management, then you will be doing everything as the root user which is not a safe way to manage your account.

Once you have set up what you need for now in Identity and Access Management, return to your AWS Management Console home page, then do another search for S3.

From here, you can create buckets using the provided wizard. You will need to make sure that the user that you have just set up has full access to the bucket so that they can read and write from/to it.

Once you're happy that your bucket has been set up, you should have been provided with the settings that you can use within your environment variables. There are many ways to use environment variables, however we will place these in a .env file within our project's root directory whilst we're in development and add them within our Heroku console as 'Config Vars' for production.

Note there are no spaces either side of the equals signs and there are no quotation marks or apostrophes required to denote strings.

# .env file

# Django Storages Settings
AWS_STORAGE_BUCKET_NAME=your-s3-bucket-name
AWS_ACCESS_KEY_ID=your-access-key-id-from-amazon
AWS_SECRET_ACCESS_KEY=your-aws-secret-key-from-amazon

There is one more important step to ensure that all of this works. In your settings file, you may have a setting like below in your settings.py file, especially if you have been doing all of your development locally up until now.

# Settings.py

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

You will need to delete (or at least temporarily comment out) the setting so that it is not being used by Django. You are now intending for S3 to be used as the default file storage for any of your media requirements as specified in the 'DEFAULT_FILE_STORAGE' setting you set earlier. This counts for both development and production.

You will now need to build your container. So far, you haven't seen the Docker build procedure which I am using for my development setup, however this specific docker-compose.yml file can be found in my GitHub repo for this website here.

$ docker-compose build

Now, it's time to test whether file uploads such as profile or post images are working as intended, so we will need to make sure that our Docker container is up and running.

$ docker-compose up

You should now be able to access your development version of your website at http://localhost:8001

Side Note: You may well use port 8000 for your development setup as that is the default Django development server, however I configure my Docker setup to specifically use port 8001. I only do this so I can visually differentiate between a standard Django development web server and my Dockerized project.

Now simply log in to your website and attempt to upload some media to your website. This is anywhere within your project's models that you have used an image field or a file field. Check the url of the media resource that has just been uploaded. If it has an Amazon S3 url, it will have worked. You can also confirm this by inspecting your S3 bucket within your Amazon AWS Management Console.

I don't know about you but I think I am going to set up any of my project's that I intend to deploy from the get-go using S3. I think it is so easy to set these things up from the start once you have a repeatable system that it can save you the headaches when it comes to deploying your application down the line. I also have a preference for installing any packages that I know I will need for my project at the start of the project and can easily add them to a Bash script that I maintain for this very reason.