Deploying and running Django apps on Heroku is really a no-brainer, except for one thing — serving static files via collectstatic
.
I run collectstatic
as usual, using heroku run
command just like how I did it for syncdb
. It worked but I’m getting 404 error when serving static files.
It turns out that running collectstatic
via heroku run
spins up a new dyno and collectstatic
is running in an isolated environment. So, all the collected static files are deployed to that location and only for this new dyno and the dyno attached to our Django app can’t access that. — Heroku support staff
Solution
The dead simple solution would be to run collectstatic
as part of the Procfile before starting the app. We need to “chain” together our Procfile commands for the web process, like so:
OK there you go, no more 404 error when serving static files from your Django app on Heroku. Plus, every time you deploy your app, newly added static files will be collected automatically.
Update
There are a lot of questions about the configurations. Cross check with my settings.py here http://pastebin.com/H43gRKsJ
Important thing here is your STATICFILES_DIRS. Make sure to include your project_name/app_name/static here. In my case, I have project_name/staticfiles for the STATIC_ROOT. Change STATIC_URL = ‘/static/’ if you want to serve from Heroku, same goes to ADMIN_MEDIA_PREFIX = ‘/static/admin/’
Finally add this to your urls.py in order to serve from Heroku.
urlpatterns += patterns('', (r'^static/(?P.*)$', 'django.views.static.serve', {'document_root': settings.STATIC_ROOT}), )
Files could be access as such:
/app/project_name/staticfiles/style.css > http://flaming-fire-999.herokuapp.com/static/style.css
/app/lib/python2.7/site-packages/django/contrib/admin/ media/css/login.css > http://flaming-fire-999. herokuapp.com/static/admin/ css/login.css
Please note that this is not the best way to serve static files but it works. One last thing, you still need to go through all these things if you are using django storages and django compressor to serve static files via Amazon S3 because django compressor needs a temp file system cache on Heroku.
I’ve just been looking into Heroku for hosting myself… I had read that the Cedar filesystem was ephemeral, i.e. wiped whenever the dyno is restarted. I thought that would preclude serving Django’s static files.
But do your commands above automatically run collectstatic when the new dyno is spun up, to reinstate them?
So it would just be user-uploaded files I’d need to run from S3 instead?
Sorry for the late, late reply.
It will run every time you do git push heroku master.
FYI, I’m using django storages & django compressor to serve static files via Amazon S3. In order for compressor to work, you’ll need a temp file system cache on Heroku, thus the solution above.
Hi there, am running into the same problem.
I wonder how you’ve setup your STATIC_URL, STATIC_ROOT and STATICFILES_DIR as I’m almost hitting my head on the wall now 🙂
Post has been updated. Check the settings.py
I have a question. I tried your snippet to get static but for me it doesn’t work.
What do you have in settings file? What is STATIC_URL and STATIC_ROOT?
With regards
Kamil
I’ve included a sample of my settings.py. Cross check with yours 🙂
Thanks 🙂 It’s working now. Founded solution.
So I have:
In Debug:
STATIC_ROOT = ‘static/’
STATIC_URL = ‘/static/’
and in urls.py
urlpatterns = patterns(”,
…####
#### urls urls urls… 😉
)
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
And it’s working great 🙂
Bingo!
Even with Kamil’s writeup, I’m not sure what you’re proposing here. Sure, running collectstatic gets all the files in one place.
But have you configured gunicorn to serve that directory separate from the Django staticfiles app?
(If you’re willing to run the staticfiles app with DEBUG on, there’s not even a need to collectstatic; it seems to serve things fine from wherever they are. But that’s “probably insecure”, says the Django docs. So I’m trying to figure out if what you Kamil is describing somehow gets heroku-nginx or gunicorn to serve the collectedstatic directory…)
With more research I’m guessing you’ve each settled on some variant like this in urls.py:
if not settings.DEBUG:
# screw the vague FUD and django doctrine against non-DEBUG static-serving
urlpatterns += patterns(”,
(r’^static/(?P.*)$’, ‘django.views.static.serve’, {‘document_root’ :settings.STATIC_ROOT}),
)
The comments for ‘django.views.static.serve’ include more of the usual admonitions about how one ‘SHOULD NOT’ do this in a ‘production setting’. But these similarly-worded-but-never-explained warnings have echoed across Django comments/docs so much they’re beginning to look like superstitions to me rather than actual best practices.
Yup. Check my updated post.
Thanks for this post! It was very helpful in getting django static files on heroku running.
However, I’ve noticed that every time I deploy a change, even without adding any new static files, collectstatic has to run every time and causes the app restart to be longer. This is sort of annoying because any users on the site would experience a long response if they made a request while this process was happening. Do you have any advice for this problem?
This is because the collectstatic command is part of the Procfile. Quick solution will be creating two versions of Procfile, one with the collectstatic command.
I can’t think of other cleaner solution.
Nice post but I have a problem. I can’t get this done. I’m getting:
unknown specifier: ?P.
My urls.py looks like:
admin.autodiscover()
urlpatterns = patterns(”,
…
…
)
if not settings.DEBUG:
urlpatterns += patterns”,
(r’^static/(?P.*)$’, ‘django.views.static.serve’, {‘document_root’: settings.STATIC_ROOT}),
)
Ok I did it. But still, heroku can’t find my static files
Make sure your STATIC_ROOT & STATICFILES_DIR are pointing to the right place.
I replace ’^static/(?P.*)$’ for just ’^static/(.*)$’ and now is working.
Don’t forget to import your settings file in your urls.py.
from yourapp import settings
Hey Mathew,
I followed what you have suggested, but I’m hitting an error: Could not import settings ‘my_django_app/settings.py’
More details here. Would appreciate any help!
http://stackoverflow.com/users/1039182/aswath-krishnan?tab=questions
Check my answer here http://stackoverflow.com/a/9848633/393499
Hi! Thank you for the blog post. However, I encountered this issue when running ‘foreman start’
13:59:40 web.1 | started with pid 2060
13:59:40 web.1 | Usage: manage.py collectstatic [options]
13:59:40 web.1 |
13:59:40 web.1 | Collect static files in a single location.
13:59:40 web.1 |
13:59:40 web.1 | manage.py: error: no such option: –noinput;
13:59:40 web.1 | process terminated
13:59:40 system | sending SIGTERM to all processes
I used this line in my Profile:
web: python manage.py collectstatic –noinput; gunicorn_django –workers=4 –bind=0.0.0.0:$PORT
It seems to be an issue with chaining the commands together on one line…any idea?
Seems like it’s an issue with foreman only. I pushed it to the Heroku repo and the app ran fine..very strange.
Yup, the chaining is meant for Heroku only.
Thanks for the post!! Question– how would I handle serving just a handful of static files on Heroku, and the majority via my main backend on Amazon S3? There are just a few files that I need on Heroku (to be on the same domain name… it’s related to browser compatibility issues), but I still want the rest of my files to be served via S3.
Any suggestions on how to go about this?
Hey Janelle,
I think that is possible. Just don’t use the {{ STATIC_URL }} template tag on the files that you want to serve from Heroku because it points to S3. Instead, use the absolute URL for these files.
For example: /app/lib/python2.7/site-packages/django/contrib/admin/media/css/login.css > http://flaming-fire-999.herokuapp.com/static/admin/css/login.css
I wrote a bit about how to do this here: http://www.mathisonian.com/weblog/django-heroku-methods-of-serving-static-files
So what’s stopping you from committing the changed static files after running a collectstatic locally? Seems to work for me so far.
In my settings.py I have:
PROJECT_DIR = os.path.dirname(__file__)
Then I use that in settings.py and urls.py to set the static directory… (Rather than a path starting in / as the manual insists on using…)
STATIC_ROOT = os.path.join(PROJECT_DIR, ‘static’)
for example…
Also, the procfile didn’t work. I got it working with:
web: python manage.py collectstatic –noinput; gunicorn -b 0.0.0.0:$PORT -w 4 [project name].wsgi:application
[project name] is the folder containing wsgi.py
The following was enough if I committed the static files:
web: gunicorn -b 0.0.0.0:$PORT -w 4 [project name].wsgi:application
Thanks,
I wonder how you would handle static files versions, the best I could have come up with is – http://balzerg.blogspot.co.il/2012/09/staticfiles-on-heroku-with-django.html
Sorry but after updating your urls.py to serve the static files doesn’t that mean that every static file requested will have to be served by both the Dyno and the Django app instead of just the Dyno.
This will push extra load on your app and that is the reason why it is marked as not good for production in the docs.
A Tip for Django Peeps on Heroku
#!/usr/bin/env python2.7
# -*- coding:utf-8 -*-
# Copyright 2013
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.conf import settings # Application Configuration
# correct spacing
def displayStaticFile(request, file):
template = ‘x/’ + str(file)
context = {}
return render_to_response(template, context, context_instance=RequestContext(request))
In your templates folder create an “x” folder and store your files in here… add this to your urls
# Static Files
url(r’^x/(?P.*)$’, ‘module.views.staticFileLoader.displayStaticFile’),
Thanks alot for this. I have scrambled on this for a while….