Handling Django static and media files in production
A comprehensive guide to managing static and media files in Django applications.
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:
- Distinguish between static and media files.
- Configure WhiteNoise to serve static files directly from your web app.
- Configure Django Storages to serve static and media files from cloud storage.
- 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:
- Django uses
STATICFILES_FINDERS
andSTATICFILES_DIRS
settings to locate static files. - Static files are then "collected" to a designated directory determined by
STATIC_ROOT
. - Every time we want to refer to static files, we then use the
STATIC_URL
prefix. - 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.
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:
- boto3 (Amazon S3).
- google-cloud-storage (Google Cloud Storage).
- azure-storage-blob (Azure Storage).
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:
- Navigate to your Sevalla dashboard.
- From the sidebar, select Object Storage, then click Add Object Storage.
- 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. - Once the storage is ready, click on it to open its details. From the sidebar, select 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.
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.