This short post is not a step by step guide on how to deploy Django on a Linux machine. It tries to explain to readers what are the goals of a reasonable Django deployment and how they can be achieved so when reading other tutorials online readers can have a better picture of what’s actually going on.
Who speaks what
With this in mind, we can treat the server software as translators that can translates a language to another.
Because our Web services are eventually consumed by web browsers or other HTTP clients, the end goal of a successful deployment is to translate everything into HTTP and providing a single HTTP “access point” who knows how to pass things to other translators based on the requested path. As discussed above, there are really only two things needed to be translated: WSGI and files.
WSGI to HTTP
Things that translate WSGI to HTTP are called WSGI servers. They receive HTTP requests and translate them into WSGI environment variables and then calls your WSGI entry point with the WSGI environment variables, for Django, the WSGI entry point can be retrieved by calling
django.core.wsgi.get_wsgi_application or is readily available in your projects’
wsgi.py file as
application. There are plenty of WSGI servers to choose from, some common choices includes
runserver command also starts a WSGI server. One thing to note though, is that
uwsgi the protocol used by
uWSGI is its own language, it is not HTTP and not WSGI, but
uwsgi can be understood by
nginx, a popular HTTP server, and you can configure
uWSGI to speak HTTP directly. To simplify things, we can treat
nginx together as a translator that translates WSGI to HTTP.
File (to WSGI) to HTTP
Now that WSGI to HTTP is handled, we have two path ahead of us for handling files on a file system.
The first one is to translate files to WSGI and because we already know how to translate WSGI to HTTP, the path from file to HTTP is also established.
The other one is to translate file directly to HTTP, this one is more efficient as you only need one translator instead of two. The problem is that (almost) all the WSGI servers can’t do this natively, meaning you can’t ask them to only translate paths that doesn’t starts with
/static. To do this, you need a more generic HTTP server or proxy, such as
caddy, you can ask them to serve files directly when the path starts with
/static, otherwise, pass the HTTP request to your WSGI server. Note that it would be impossible to do this when we don’t have access to the HTTP server configuration, for example, when using Heroku.
Django’s built-in development server goes the first path, it turns all HTTP requests to WSGI and inside WSGI application if it sees the requested URL matches that of a static file, it then reads the file and returns its content. There are other libraries that does this better than Django’s built-in server, such as
whitenoise. This approach is not completely unusable, especially when you have a CDN server that caches all static resources, so future requests only hits the CDN server not your Django applications.
Non Django-specific goals and tools
When doing a production deployment, it’s not enough to just Django running. We need users to visit our website though a domain name, so a DNS record that points to our server will be required and port 80 will need to be open on the server. For security, we would like to provide HTTPS accessibility too. To do this, we can use
nginx with some plugin to automatically request SSL certificates from
Let's Encrypt. Cloudflare is a popular CDN/DNS service provider that have built-in support for HTTPS and caching.
It is also desired to have some kind of process manager to run our WSGI servers instead of running it on the foreground in the shell. Process managers allows us to monitor the status of servers, auto start servers when machine reboots, scale servers to have more processes and other benefits.
supervisor is a popular option in Django community as a simple process manager.
As mentioned above, uWSGI can be configured to speak HTTP directly. Turns out, it can also be configured to serve static files. So you really can do a Django deployment with only uWSGI. Here’s an example configuration:
http = 0.0.0.0:80
static-map = /static=/myproj/static
static-map = /media=/myproj/media
chdir = /myproj
module = myproj.wsgi
home = /myproj/venv # this is the virtualenv path
master = true
processes = 10
vacuum = true