Understanding static files in Django + Heroku

Ryan von Kunes Newton
3 min readMay 7, 2019

--

Understanding Django’s static files is a bit confusing. There is certainly quite a bit of documentation and variables out there, so I’m writing this for my future self.

The important file here is the settings.py file that every Django project has. We’ll dig into DEBUG, STATIC_ROOT, STATIC_URL, and STATICFILES_DIRS.

DEBUG

This is an important setting for the entire app. You want this to be False on production. If DEBUG = True then ./manage.py runserver will handle serving static files on its own. However, when DEBUG = False runserver will not do this for you, and that’s where some complexity arises.

STATIC_URL

The static url is the url path where a client or browser can access static files. So withhttps://www.example.com as your url, if STATIC_URL = 'mystaticpath' and you had an image named test.jpg, you would access the image at https://www.example.com/mystaticpath/test.jpg.

STATIC_ROOT

This generates the directory where files static files are placed when you run ./manage.py collectstatic. For example, if your root folder was myawesomesite and STATIC_ROOT = os.path.join(BASE_DIR, 'placefileshere'), after running collectstatic a folder would have been generated in myawesomesite/placefileshere/ with staticfiles.json and also test.jpg from above located in it.

I do not believe STATIC_ROOT has any impact when DEBUG = True, as you don’t need to run collectstatic in development.

STATICFILES_DIRS

This determines the locations where static files are being pulled from. For example:

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'polls/files/')]

Any files in polls/files/ would be in and accessible as static files. The directory structure within it is maintained as well, so for example with

--polls
----files
------img
--------test.jpg

and STATIC_URL = 'mystaticpath' you would access test.jpg by going tohttps://www.example.com/mystaticpath/img/test.jpg.

Having multiple STATICFILES_DIRS will merge them all into one, e.g.:

STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'polls/files/'),
os.path.join(BASE_DIR, 'people/otherfiles/')
]

and

--polls
----files
------img
--------test.jpg
--people
----otherfiles
------css
--------styles.css

If you ran collectstatic you’d end up with

--placefileshere
----img
------test.jpg
----css
------styles.css

and likewise access the css file at https://www.example.com/mystaticpath/css/styles.css. Due to the merging nature of this, you have to be wary of namespacing conflicts.

Involving Heroku

Note: if you’re not deploying to Heroku, you can ignore this section.

Deploying to Heroku adds additional complexity to the process of static file handling. They have a some docs about here, however, there’s actually a few additional things going on.

Chances are you’re using the django-heroku library and setting django_heroku.settings(locals()) as suggested in Configuring Django Apps for Heroku.

What I’ve discovered is that STATIC_URL and STATIC_ROOT are actually overwritten by heroku to STATIC_URL = '/static/' and STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles'). Even if you have a different STATIC_ROOT in your settings if you were to run heroku run python manage.py collectstatic, they will use staticfiles as the STATIC_ROOT.

The suggestion in their docs does not impact, but is just matching the settings locally to what Heroku is overriding it to.

Conclusion

Static files are a relatively complicated thing in Django. There are a lot of moving parts, and I’ve linked many of the docs. If you’re interested in digging deeper, I’d suggest looking at WhiteNoise, a library that helps with static file serving for python.

--

--