Try Sevalla today and get $50 free credits

Blog

Handling Django static and media files in production

A comprehensive guide to managing static and media files in Django applications.

·by Nik Tomazic

This tutorial explains how to serve Django static and media files in production. It covers two approaches: WhiteNoise and Django Storages.

Objectives

By the end of this tutorial, you should be able to:

  1. Distinguish between static and media files.
  2. Configure WhiteNoise to serve static files directly from your web app.
  3. Configure Django Storages to serve static and media files from cloud storage.
  4. Choose the best file-serving strategy for your project.

Static files and media files

In Django projects, we have three types of files: source code, static files, and media files.

Source code

Source code refers to the Python (.py) files that define the logic of your web application. This includes models, views, tasks, utilities, and other core components.

Static files

Static files are the additional assets, such as images, JavaScript, CSS, and other client-side resources. These have to be shipped along with the source code. To simplify working with static files, Django provides the staticfiles module.

In development, static files are served directly by the Django development server.

However, in production, the process is a bit more complicated:

  1. Django uses STATICFILES_FINDERS and STATICFILES_DIRS settings to locate static files.
  2. Static files are then "collected" to a designated directory determined by STATIC_ROOT.
  3. Every time we want to refer to static files, we then use the STATIC_URL prefix.
  4. It's up to us to serve the static files (we'll look at different approaches later).

The key manage.py commands for working with static files are:

  • collectstatic: Collects or copies static files to the designated folder.
  • findstatic: Locates static files within the source code.

Media files

Media files are the files uploaded by users, such as images, documents, or other attachments. These are usually handled using Django's FileField and ImageField.

Media files are sometimes referred to as just "files" in Django docs.

Since we don't know what users will upload, we must exercise caution when dealing with these files. We have to ensure proper validation, sanitization, and storage practices to prevent security vulnerabilities. Optimally, we want to store and serve them from an external server.

Media files are handled analogous to static files, except that there is no "collection" step. The relevant two settings are: MEDIA_ROOT and MEDIA_URL.

Project introduction

To make the tutorial easier to follow along, I've prepared a simple web app that we'll use as a starting point. The web app offers two endpoints, one for uploading files and one for viewing them.

Feel free to follow along with your own project that uses static and/or media files.

First, clone the base branch of the GitHub repository:

$ git clone [email protected]:duplxey/sevalla-django-assets.git -b base --single-branch
$ cd sevalla-django-assets

Create a virtual environment and install the requirements:

$ python3 -m venv venv && source venv/bin/activate
$ pip install -r requirements.txt

Next, migrate and populate the database:

$ python manage.py migrate
$ python manage.py populate_db

The populate_db command will create a few sample uploads and a super user:

user: admin
pass: password

Lastly, start the development server:

$ python manage.py runserver

Your web app should be accessible at http://localhost:8000. Open the link in your web browser, and you should see three demo uploads. Ensure everything works by uploading a file yourself.

Django Sevalla Assets

Each of the approaches we'll look at can be used independently. That means that you should reset/stash the changes before switching from one approach to another:

$ git reset HEAD --hard

WhiteNoise

This option is appropriate for only static files.

WhiteNoise is a simple package that enables your web app to serve its own static files, making it a self-contained unit. The package is helpful for small to mid-sized projects, as it allows you to serve static files without adding any additional complexity.

Best of all, it automatically configures caching and is designed to work nicely with CDNs.

Installation

Start by installing it via pip:

$ pip install whitenoise==6.9.0

Don't forget to add the package to your requirements.txt.

Next, add the WhiteNoiseMiddleware right under the SecurityMiddleware:

# core/settings.py

MIDDLEWARE = [
    # ...
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ...
]

Finally, slightly modify STORAGES to enable compression and make files forever cachable:

# core/settings.py

STORAGES = {
    "default": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
    },
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}

That's it!

The static files should now be served by WhiteNoise. To ensure everything works, navigate to the admin page at http://localhost:8000/admin and ensure CSS/JS still loads.

Django Storages

This option is appropriate for both static and media files.

Django Storages is an easy-to-use package that provides a collection of custom storage backends for Django. It natively supports AWS S3, Azure Storage, Google Cloud Storage, and SFTP, among others.

Thanks to its robustness and reliability, django-storages has become the de facto standard for file storage in Django projects. Moreover, the package can be combined with a CDN for efficient delivery.

Finally, another benefit of using cloud storage over traditional file systems is that it allows your media files to persist in containerized setups (e.g., PaaS and CaaS). In these setups, file systems are typically ephemeral, meaning that your media files are deleted with every application restart.

Django Storages should suit almost any project; however, if you need an even higher degree of customizability, you can look into the official cloud SDKs and implement your own solution:

In this tutorial, we’ll configure Django Storages with Sevalla Object Storage, an S3-compatible service. The same configuration can be adapted to AWS S3, Google Cloud Storage, Azure, or any other S3-compatible provider.

What is Sevalla?

Sevalla is a Platform as a Service (PaaS) that supports application hosting, SQL and NoSQL databases, static sites, and S3-compatible object storage. It’s designed to make deployment straightforward while supporting multiple build types, including Nixpacks, Buildpacks, and custom Dockerfiles.

Sevalla also provides features such as horizontal scaling and auto-scaling, DDoS protection via Cloudflare, and free SSL certificates.

New accounts include a $50 free credit on sign-up, which is useful for experimenting or running small projects at no initial cost.

Create Object Storage

To set up object storage in Sevalla:

  1. Navigate to your Sevalla dashboard.
  2. From the sidebar, select Object Storage, then click Add Object Storage. Sevalla Create Object Storage
  3. Next, choose a name for your storage and select the region closest to your users for optimal performance.
    Sevalla will automatically provision the storage in just a few moments.
  4. Once the storage is ready, click on it to open its details. From the sidebar, select Settings. Sevalla Object Storage Settings

Here, you’ll find the credentials you need for your Django configuration:

  • Location
  • Bucket name
  • Endpoint
  • Access key
  • Secret key

And that’s it — you’ve successfully created and configured object storage on Sevalla in just a couple of clicks.

Configure Django

Moving along, let's connect our Django project to the object storage.

First, install the django-storages[s3] package:

$ pip install django-storages[s3]==1.14.6

Note the [s3] extra and don't forget to add it to requirements.txt.

Next, create storages.py in the main app with the following contents:

# core/storages.py

from storages.backends.s3boto3 import S3Boto3Storage


class StaticStorage(S3Boto3Storage):
    location = "staticfiles"
    file_overwrite = True


class MediaStorage(S3Boto3Storage):
    location = "mediafiles"
    file_overwrite = False

The code defines two storages — one for static and one for media files. In each storage, we've provided the location of where the files should be saved and file_overwrite, which determines whether files with the same names should be overwritten.

Lastly, replace the STORAGES option in settings.py like so:

# core/settings.py

if DEBUG:
    STORAGES = {
        "default": {
            "BACKEND": "django.core.files.storage.FileSystemStorage",
        },
        "staticfiles": {
            "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
        },
    }
else:
    AWS_S3_REGION_NAME = "<object-storage-location>"
    AWS_STORAGE_BUCKET_NAME = "<object-storage-bucket-name>"
    AWS_S3_ENDPOINT_URL = "<object-storage-endpoint>"
    AWS_S3_ACCESS_KEY_ID = "<object-storage-access-key>"
    AWS_S3_SECRET_ACCESS_KEY = "<object-storage-secret-key>"

    STORAGES = {
        "default": {
            "BACKEND": "core.storages.MediaStorage",
        },
        "staticfiles": {
            "BACKEND": "core.storages.StaticStorage",
        },
    }

Ensure to replace the <placeholders> with the credentials from the previous step.

With this configuration, we're telling Django to use local storage for static and media files in development, and cloud storage in production. If you want, you can also create two object storages, one for development and one for production.

Ensure everything works by disabling the DEBUG, and collecting the static files:

$ python manage.py collectstatic

Then, upload a sample file to the web app.

If everything works correctly, you should see that the static files have been copied to the staticfiles folder, and the media files to the mediafiles folder.

Sevalla Object Storage Populated

Additionally, Django is no longer responsible for serving static and media files. It simply needs to generate a URL to the cloud object resource with the proper security parameters.

# before: Django generated URLs for local file system and served the files
http://localhost:8000/static/admin/js/nav_sidebar.js
http://localhost:8000/media/cat-picture.png

# now: Django generates URLs to the cloud object resource (with proper security creds)
https://r2.cloudflarestorage.com/<bucket-name>/staticfiles/admin/js/nav_sidebar.js?...
https://r2.cloudflarestorage.com/<bucket-name>/mediafiles/cat-picture.png?...

Summary

In this article, we've learned how to handle static and media files and explored different strategies for serving them.

As a general guideline, use WhiteNoise if your app only requires static files. However, if your app also handles user-uploaded content, opt for Django Storages, as it is more secure, efficient, and ensures media files persist in containerized setups.

The final source code is available on GitHub.

Deep dive into the cloud!

Stake your claim on the Interwebz today with Sevalla's platform!
Deploy your application, database, or static site in minutes.

Get started