23
Serving static files with Django
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: "This method is grossly inefficient and probably insecure, so it is unsuitable for production".
So, what's the best way to do it?
Here's a quick refresher for what I mean by static and media files in the context of Django, as they are handled differently in some cases.
By static files, I 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, I mean files that are uploaded by your users and afterward served as is. A typical example are photos uploaded by your users.
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.
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.
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.
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.
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.
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.
23