Note: this is a repost of something I originally wrote while at Refer.ly. You can find the original post here.
Recently, I was tasked with overhauling the asset pipeline for a Django application being hosted on Heroku. The idea being that we want static files to deploy seamlessly when we push new code and yet maintain a different set of static files while working in development, staging, and on production. Additionally, user-uploaded media should work as expected: on production it should be uploaded to S3 and in development it should be stored locally.
This was my first major task in this code base, and needless to say, there was a lot to learn. While I have experience running Django on a standard stack, this would be my first time working with Heroku, and I was surprised to find that this wasn’t nearly as straightforward as I expected. Hopefully my experiences here can help others get this set up correctly from the start.
When you’re working in the development environment (
and using 1
DEBUG = True
), Django serves all static content for you from the 1
manage.py runserver
, allowing you to make changes to the static files and view them with a simple refresh.1
STATIC_URL
Now, when you deploy to production and set
, Django no longer serves static assets from 1
DEBUG = False
– instead, it expects you to serve these asset files using your webserver. This is good, because using Django to serve static files is a huge waste of resources. However, it means that every time you deploy new static assets, you have to manually copy them to the static root your website has configured– something you’ll probably want to automate in your deploy process.1
STATIC_URL
But wait: someone already figured out the code for deployments, and you don’t have to! As long as you’re using the default
app, all you have to do is run 1
django.contrib.staticfiles
, which collects all of your static assets and places them in the 1
python manage.py collectstatic
you defined, which can then be served by your faithful webserver of choice. But what if you don’t control the webserver?1
STATIC_ROOT
Now, Heroku is smart enough to automatically run collectstatic for you when you deploy code, and even includes some troubleshooting advice, but unless you define a Django view to serve files from the
, you won’t actually be serving these files. And if you do use Django to serve these files, you are wasting valuable dyno hours on static files.1
STATIC_ROOT
Unfortunately, Heroku’s guide on using Django and static files has no actual details on how you should configure Django to serve static files. Even worse, a quick google search for “heroku static files django” returns a number of solutions that directly contradict the advice given in the Django docs, which is deserves to be quoted verbatim here(emphasis theirs):
This view[static file serve] will only work if DEBUG is True.
That’s because this view is grossly inefficient and probably insecure. This is only intended for local development, and should never be used in production.
Why Django even includes instructions for using this code in production is beyond me. Thankfully, a google search for “proper way to handle static files for django on heroku” turns up a stack overflow answer from intenex with the correct solution: use
. By implementing a different storage backend, such as S3, 1
django-storages
will automatically collect your static files to S3 and serve them faithfully.1
collectstatic
Add
and 1
django-storages
to your 1
boto
and run 1
requirements.txt
to install them.1
pip install -r requirements.txt
Add
to your 1
storages
in 1
INSTALLED_APPS
.1
settings.py
Add AWS keys to your Heroku config if you haven’t already:
1
2
heroku config:add AWS_ACCESS_KEY_ID=YOURACCESSKEY
heroku config:add AWS_SECRET_ACCESS_KEY=YOURSECRETACCESSKEY
Create a file called
in the project root dir and add:1
s3storages.py
1
2
3
4
5
6
7
8
9
10
11
from storages.backends.s3boto import S3BotoStorage
# Define bucket and folder for static files.
StaticStorage = lambda: S3BotoStorage(
bucket='yourbucket',
location='assets')
# Define bucket and folder for media files.
MediaStorage = lambda: S3BotoStorage(
bucket='yourbucket',
location='media')
update
with the following:1
settings.py
1
2
3
4
5
6
7
8
9
10
11
12
if DEBUG:
# Development storage using local files.
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = '/path/to/development/media'
if not DEBUG:
# Production storage using s3.
DEFAULT_FILE_STORAGE = 's3storages.MediaStorage'
STATICFILES_STORAGE = 's3storages.StaticStorage'
STATIC_URL = 'https://s3.amazonaws.com/yourbucket/assets'
MEDIA_URL = 'https://s3.amazonaws.com/yourbucket/static/'
Inside of templates, use
as your roots and they will work seamlessly on production and development.1
and
You’ll need to tell heroku to allow deployment scripts to access the AWS credentials in the environment by running 1
heroku labs:enable user-env-compile -a myapp
What about user uploaded media? If you’re using the Django forms module, and using FileField on your models, django-storages will automatically store your media in the correct place: S3 for production, and the local filesystem for development.
Use absolute S3 paths in js/css: Since you can’t use template variables in css/js, you’ll have to upload images to S3 and use the full path to the image, i.e.
. Using a preprocessor may allow you to get around this, but I’m going to save that for a follow-up post.1
background-image: url(http://s3.amazonaws.com/yourbucket/assets/image.png);
Careful: if you’re not using
(which automatically adds the default 1
render()
) or specifying a 1
RequestContext
in your 1
context_instance
calls, you need to pass 1
render_to_response()
and 1
MEDIA_URL
values to the template. If you’re using templates to generate emails, double check them!1
STATIC_URL