2024-07-29

Setting up DigitalOcean Spaces for Django Media

At DigitalOcean, when you need large amounts of static data (images, documents, or videos), you have two options: Volumes Block Storage and Spaces Object Storage. In case of Volumes, you mount an extra hard drive to your server and use the file system to manage your files. Whereas with Spaces, you store files in the cloud and use a special API to create, read and delete files there.

Spaces Object Storage is comparable with AWS S3 Cloud Object Storage. It even supports the same API for dealing with data there.

Here are some benefits of using Spaces:

  • The price of Spaces is at least twice as low as the one of Volumes.
  • Content Delivery Network (CDN) is available for media file caching.
  • The configuration of Spaces is easier and the user interface is more user-friendly than the one of AWS S3.
  • It is relatively easy to start using Spaces for media files with the django-storages package.

I will walk you through setting up Spaces for your media files.

Create Spaces Object Storage at DigitalOcean

When creating Spaces Object Storage at DigitalOcean, you will be asked for these values:

  • Data center - choose the one closest to your business for legal reasons. For example, I chose Frankfurt for PyBazaar.
  • Enable CDN for caching - enable it for server-side caching.
  • Spaces bucket name - lowercase name for your bucket (e.g. "pybazaar")
  • Select a project - Project name for grouping your DigitalOcean resources (e.g. "PyBazaar")

For the created Spaces instance, I keep the settings unchanged:

  • File Listing: Restricted
  • CDN: Enabled
  • CORS Configurations: Unset

This will create an instance, which resources, aka media files, can be accessed under https://pybazaar.fra1.digitaloceanspaces.com and https://pybazaar.fra1.cdn.digitaloceanspaces.com

Create API keys for spaces at DigitalOcean

Now go to APISpaces Keys and choose Generate New Key.

There you'll generate an Access Key and Secret Key. You'll need those in the Django settings and for example Transit app for easy mass file management.

Connect Django to Spaces

Install django-storages and boto3 to your Django project.

(venv)$ pip install boto3
(venv)$ pip install django-storages[s3]

Add the STORAGES setting:

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage",
        "OPTIONS": {
            "bucket_name": "pybazaar",
            "access_key": get_secret("SPACES_ACCESS_KEY"),
            "secret_key": get_secret("SPACES_SECRET_KEY"),
            "region_name": "fra1",
            "endpoint_url": "https://pybazaar.fra1.digitaloceanspaces.com",
            "default_acl": "public-read",
            "location": "media",
            # required for the correct storage.exists() functioning
            "file_overwrite": False,
            # don't append any authentication parameters to the files.
            "querystring_auth": False,  
        },
    },
    "staticfiles": {
        # For static files, use file-system storage 
        "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
        # Or Whitenoise storage
        # "BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
    },
}
MEDIA_URL = "https://pybazaar.fra1.cdn.digitaloceanspaces.com/pybazaar/media/"

The implementation of get_secrets() depends on your needs. You can import values from environment variables or from a JSON or INI file. Here's a version I am using in my projects. Just make sure not to include your secrets in the Git repository.

MEDIA_ROOT is not participating in the game of django-storages.

For static files, I recommend using File System or Whitenoise, althought theoretically you could also use django-storages for them too.

Try connecting your local environment to spaces at first, then check it remotely.

If you use a rich-text editor supporting images or links to documents, you should ensure that the media paths don't change when you dump your pages from production to development. Otherwise, the images and links will be broken. To ensure that, set MEDIA_URL to "/pybazaar/media/" locally when you use the file system storage locally again.

Use the Storage API for all your file management

Use the Django Storage operations instead of file-system operations for all your media file operations: listing directory contents, creating a file, reading a file, updating file content, deleting a file, or checking the existence of a file.

For example, instead of this code:

import os
from django.conf import settings

with open(os.path.join(settings.MEDIA_ROOT, "README.txt"), "w") as f: 
    f.write("Hello, World!")

use this one:

from django.core.files.storage import default_storage
from django.core.files.base import ContentFile

default_storage.save("README.txt", ContentFile("Hello, World!"))

Ensure that django-imagekit works well

If you are using django-imagekit or another image manipulation library, make sure that you use the latest version that supports the STORAGES setting.

For older Django versions (< 4.2), you might need to set AWS_* settings instead of the STORAGES dictionary. See the django-packages docs here.

Upload some files

You can upload files for Spaces one-by-one at DigitalOcean console. Or better, use Transit v5 on MacOS.

Add new connection and choose Amazon S3

  • Address: fra1.digitaloceanspaces.com
  • Access Key ID: [your access key]
  • Secret: [your secret key]
  • Remote Path: /pybazaar/pybazaar/media

Upload some files there. Check if they are accessible from the CDN endpoint URL.

Set up a subdomain of yours

To have a dedicated subdomain of yours pointing to the spaces, e.g. https://media.pybazaar.com, you can follow the instructions in this article.

If you manage your DNS settings on DigitalOcean, it's not a big deal: you just click a few buttons to set the CNAME record and enable Let's encrypt certificates.

But if you manage your DNS elsewhere, you will have to manually set and regularly update your SSL settings, and set a CNAME record to point your subdomain (e.g. media.pybazaar.com) to their CDN endpoint (e.g. pybazaar.fra1.cdn.digitaloceanspaces.com).

Final words

Using django-storages you can relatively simply replace your file system with cloud-based storage such as Spaces Object Storage at DigitalOcean.

DigitalOcean gives $200 in credit over 60 days for everyone who will use my affiliate link to signup. If you are planning a website with lots of media files, it's worthy to have a try.


Cover photo by gdtography