Django/Python
Serving static files with Django
December 4th, 2021
In this article:
How do you serve your static and media files with Django? Here are a few use cases and popular ways to do it.
As you start your Django journey with a new project, Django's runserver
helpfully handles your static files
for you. But the documentation emphatically tells you not to do that in production use:
"This method is grossly inefficient and probably insecure, so it is unsuitable for production".
So, what's the best way to do it?
Static and media files
Before we begin, here's a quick refresher for what we mean by static and media files in the context of Django, as they are handled differently in some cases.
By static files we mean files that are part of your project (committed to your code repository) and are served as-is to your visitors. These may be images, CSS files, JavaScript files, PDFs, audio or video files - anything that's served as is, but is built-in to your project.
By media files we mean files that are uploaded by your users and afterward served as is. A typical example are photos uploaded by your users.
Local and cloud storage
Both static files and media files can be stored either locally, on the same server that hosts the Django project, or somewhere else, for example on a cloud storage service.
Back in the day when most projects were deployed on a dedicated or virtual server, most files were stored locally. Today, if using services or tools that treat the deployed server as an immutable (read-only) image, this is no longer possible. Examples of this approach are Heroku and docker. In this case, storing media files locally is impossible, requiring the use of some kind of cloud storage.
Serving via a web server
If your Django instance sits behind a web server such as Nginx or Apache and your files are stored locally, the webserver can serve them directly.
Assuming your static files are located (collected) in /var/app/static
(so that's your STATIC_ROOT
in Django
settings) and served under /static/
prefix (that's your STATIC_URL
in Django), here's an example Nginx
config to serve them:
location /static/ {
root /var/app;
}
If you'd also want to serve media files, assuming MEDIA_URL
is /media/
and MEDIA_ROOT
is /var/app/media
,
you'd add another block:
location /media/ {
root /var/app;
}
As an aside: since Nginx appends the URI path to the document root, it's useful to have the STATIC_ROOT
end
in STATIC_URL
and MEDIA_ROOT
end in MEDIA_URL
- it makes configuration simpler.
For Apache, the configuration would look something like this:
DocumentRoot /var/app;
Alias /static /var/app/static;
Alias /media /var/app/media;
<Directory /var/app>
Deny from all
</Directory>
<Directory /var/app/static>
Allow from all
</Directory>
<Directory /var/app/media>
Allow from all
</Directory>
Note that both examples are minimal and ignore things like compression, setting expiration headers, and so on.
Serving from cloud storage
If you're using cloud storage such as Amazon S3, you probably use the excellent
django-storages. It makes serving static files directly from the
cloud service easy. In your Django settings file, you just set your STATICFILES_STORAGE
(for static files) and
DEFAULT_FILE_STORAGE
(for media files) to your preferred backend.
Example for Amazon S3:
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
When you call manage.py collectstatic
as part of your deployment procedure, the storages engine will upload
all static files to S3. Media files will be uploaded to S3 when you try to save them server-side.
Again, note that this is a minimalistic example - see django-storages documentation for detailed installation instructions and all the options.
Serving static files via whitenoise
If you're serving your media files from a cloud service, you still have the option to serve static files locally. You may want to do that to simplify deployment or rollback procedure or for some other reason.
If you're behind a web server you can use it to serve static files as explained previously. But if you're using docker or deployed to a platform like Heroku, you may not be able to do that. Instead, you can serve them directly from Django, using whitenoise.
Enabling whitenoise is as simple as adding it to the list of middlewares in Django settings:
MIDDLEWARE = [
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...
]
Note the placement of middleware matters - be sure to read the docs.
Compression and caching
Both django-storages
and whitenoise
support compression and manifest storage for your content.
Compressing your static files where it makes sense can dramatically decrease their size and thus load time of your pages. CSS and JavaScript files are good candidates for compression, while images, audio, and video files are already compressed.
If you enable manifest storage, the collectstatic step will rename the files to a unique name. For example,
script.js
might be renamed to script.f9f204dfa.js
, where f9f204dfa
is a manifest, or checksum, of
the file contents. The manifest changes each time the file contents change, meaning each change to a file
will generate a new, unique, name.
This, in turn, means we can tell the browser (and a CDN if you use one) to cache files forever. When you deploy a new version of your project that changes the file, a new file name will be used and the browser (or a CDN) will know to fetch the new file.
An important caveat here is that you must always access the static files using the {% static %}
template
tag. The template tag will rewrite the name to use the actual (current) manifest. If you hardcode path
like script.js
in your template, it will not be found
Use with CDN
All of the above approaches can be used irrespective of whether you're using a content delivery network (CDN) or not. If you do use CDN, using manifest storage will help you even more.
A real-world example
If you'd like to see a real-world example of serving files with django-storages or whitenoise, try our
Django project builder. As you change the relevant options in the
builder on the left-hand side, check the generated Django settings on the right (be sure to select
settings/base.py
for previewing to see the changes).