On Static Media and Django
We all know not to serve static media (images, CSS, Javascript, etc.) in production directly from Django. Thankfully, Django gives us some nice settings like MEDIA_URL and MEDIA_ROOT to make serving them a lot less painless. Lately, however, I’ve come to realize that these settings shouldn’t really apply to all static media.
Not All Static Media Is Created Equal
Static media really comes in two flavors. Media that is part of your site like your stylesheets and user generated media or files that are uploaded to the site once it is live. We don’t want to serve all this media from the same place for a couple of reasons:
- Our source checkouts shouldn’t be crufted up by stuff our users are uploading
- User generated content and source code should live in two different places on the filesystem
We could fix the first problem with some .gitignore, but that doesn’t solve the filesystem issue.
Solution: Segregate Your Static Files
The answer is to segregate your static files. Django already does this with ADMIN_MEDIA_PREFIX. We use two additional settings in our Django projects, STATIC_URL and STATIC_ROOT. This lets us put user generated content in MEDIA_ROOT outside our project while still serving STATIC_ROOT from inside our source repo.
Implementation
We use Nginx as a front-end proxy to most of our sites and add this stanza to serve all our static media:
location /s/ {
alias /var/www/domainname.com/;
}
Then we add three directories to /var/www/domainname.com/:
- admin, a symlink to django/contrib/admin/media
- static, a symlink pointing inside our project repo respectively
- media, a new folder that stores all the user generated content.
Our project settings file looks like this now:
MEDIA_ROOT = '/var/www/domainname.com/media/' MEDIA_URL = '/s/media/' STATIC_ROOT = '/path/to/project_src/static/' STATIC_URL = '/s/static/' ADMIN_MEDIA_PREFIX = '/s/admin/'
and we slap the following code into our urls.py to serve media during development:
from django.conf import settings if getattr(settings, 'LOCAL_DEV', False): urlpatterns += patterns('django.views.static', (r'^%s(?P<path>.*)' % settings.STATIC_URL, 'serve', {'document_root': settings.STATIC_ROOT}), (r'^%s(?P</path><path>.*)' % settings.MEDIA_URL, 'serve', {'document_root': settings.MEDIA_ROOT}), ) </path>
This has worked very well for us, how do you tackle the issue (or is it not an issue to you)?
Comments
Got something to say?
This was written on November, 13 2008 and is filed in django.
Our Products
Categories
- SEO
- accessiblity
- code
- company news
- django
- gondola
- open source
- portfolio
- presentation
- screencast
- software
- subversion
- trailmapping
- wordpress
Archives
- June, 2009
- April, 2009
- February, 2009
- December, 2008
- November, 2008
- September, 2008
- August, 2008
- July, 2008
Elsewhere
What we’ve been up to online
-
@37signals, seeing a number of 500 errors clicking around Basecamp right now http://skitch.com/t/u7e
Pete, 1 day, 17 hours ago -
Basecamp 500 Internal Server Error
Pete, 1 day, 17 hours ago -
Gmail broke plain text replies. Plz fix! http://bit.ly/43nd3q
Pete, 1 week ago -
Building an Open Source Consulting Company
Pete, 1 week, 3 days ago -
< 30% of applicants correctly followed the instructions. Should have added "attention to detail" & "ability to follow instructions" as reqs
Pete, 1 week, 6 days ago -
Django snippets: Sorl Thumbnail + Amazon S3
Pete, 1 week, 6 days ago -
Lincoln Loop is still looking for a Project Manager. Interested? http://authenticjobs.com/jobs/3688/
Pete, 2 weeks ago -
Use PERT technique for more accurate estimates
Taking a weighted average of the most pessimistic, most optimistic, and most likely estimates of a task to get a realistic estimate of the time it will take.
Pete, 2 weeks, 3 days ago -
Evidence Based Scheduling - Joel on Software
Interesting approach to software estimation.
Pete, 2 weeks, 3 days ago -
Less Wrong: Planning Fallacy
People are terrible planners/estimators and there is evidence to prove it.
Pete, 2 weeks, 3 days ago -
A reminder of how simple business can be when you don't make it complicated - (37signals)
Refreshing, especially after after spending 2 days wading through client contracts and work orders.
Pete, 3 weeks, 5 days ago -
We're looking for a part-time Project Mgr to help us juggle the workload. Interested? info+pm@lincolnloop.com
Pete, 4 weeks, 1 day ago -
pushed to master at lincolnloop/django-protected-files
Pete, 1 month ago -
@richleland do you have libjpeg installed?
Pete, 1 month ago -
I have seen the future and it is Google Wave http://wave.google.com
Pete, 1 month ago


Looks like some of my code got BeautifulSoup’ed. Both the regexes in the urls.py are
r'^%s(?P<path>.*)'First of all i use a separate domain for static/media, preferable 2 because it speeds up your site (default settings in all browsers is to have max 2 concurrent connections to the same domain) something like static1.foo.com static2.foo.com which resolves to the same ip. You dont want more than 2 domains for statics because domain resolving on many domains slows down your site (you can read about this in steves sounder (creator of yslow)s book High Performence Web Sites.
My preferred setup then looks like this. one or multiple apache instances with mod_wsgi for the django app, and then one or multiple nginx instances for the statics depening on how large the site is, and one nginx acting as a proxy infront of the whole setup. If its a small site i serve the statics from the same nginx which acts as proxy.
And for separating user generated statics from the sites i do something like this:
location / { root /home/foo/site/media;
}
location /static { root /home/foo/upload;
}
Great post! I love your method here, and I’m going to start using it going forward. Thanks so much for sharing such a useful best practice!
I know this is a bit old, but I noticed a problem with the code above, since I tried to use it in my own app.
I had to set up my patterns like this:
urlpatterns += patterns('django.views.static’, (r’^%s(?P.*)’ % settings.STATIC_URL[1:], 'serve’,
{'document_root’: settings.STATIC_ROOT}),
(r’^%s(?P.*)’ % settings.MEDIA_URL[1:], 'serve’,
{'document_root’: settings.MEDIA_ROOT}),
)
With the [1:] slice on STATIC_URL and MEDIA_URL, because otherwise the initial slash in those URLs will cause the URLs not to work.
I’ve included it in:
http://github.com/mikl/djime/commit/874829443423aae83a2f0af1d4b7c39cfde8f37d – thanks :)
@Andreas – I am a bit skeptical of the max 2 concurrent connections thing. I have observed safari 3 and firefox load multiple files simultaneously, and generally from a browser maker’s perspective this seems like it would make the browser feel slower and less snappy (which is not what they are motivated to do).
I looked at the oreilly website for the book you mention, and i don’t see that seperation in the “core 14 priniciples”, but I do see “Reduce DNS Lookups”, which would make me lean towards centralize everything on one domain.
However, I am always very interested in making our sites load faster, so if you have any references about this specifically, I am intersted in it, so in the off chance you hit this thread again, please post some!
The Google Browser Security Handbook has some numbers on concurrent connections in various browsers:
http://code.google.com/p/browsersec/wiki/Part2#Simultaneous_connection_limits
How do you handle reusable apps (i.e. apps that multiple projects are using) that provide their own static media? Do you symlink to the app media folder from the project media folder? And each app always uses STATIC_URL instead of MEDIA_URL? If that’s the case, how do you handle apps written by someone else that also happen to provide static files?
@Erik
Good question and one I don’t have a great answer for yet. Things I’ve done in the past: