elias Published Feb. 22, 2023 · 5 min read

Deploying a Django app with Gunicorn and SQLite

A decorative hero image for this page.

Deploying your Django project is a tedious task that those who like coding normally prefer to avoid. In this post we explore how to get a Django app running in no time with proven and reliable technologies by staying out of the hype.

deployments devops django gunicorn

When it's time to share your project with the rest of the world you're faced with dozens of options to Deploy your Django app. In this post we look at a straightforward setup to deploy your app as easily and cheaply as possible.

At Negative Epsilon, we like to use established tech and stay away from market trends which may end up being short lived. We believe in using the right tool for the job, and since not everyone has the same availability requirements as Amazon, we prefer to avoid complex deployment setups with too many moving parts.

A minimal Django project

For this post we'll be deploying the classic Polls project in the Django tutorial. Apart from the Django app itself, it just needs a database, which makes it the prototype for any Django app that's just starting to be developed.

This app allows users to create and answer polls, but not much more. Specifically, it does not require asynchronous task processing, sending emails, caching or complex monitoring. These are certainly necessary for more complex apps with more traffic, but for now we'll focus on how to set up the least amount of infrastructure necessary to deploy this app.

Serving HTTP traffic

Django (and other Python frameworks) don't speak HTTP directly, so we need an application server to connect them to the internet. While developing your Django project, it's likely that you have used python manage.py runserver to preview your application locally. This method is very strongly not recommended for production environments and even if you decide to use it, it'll probably take you some time to remove all the safeguards that the Django team put in place to prevent people from using it in production.

We'll use one of the recommended application servers instead, gunicorn. We prefer it over the rest of the options described in the Django documentation because it's far easier to set up, partly because it is not as flexible as, say, uwsgi.

Configuring gunicorn is very straightfoward, just

  1. Add gunicorn==20.1.0 to your requirements.txt file.
  2. Create a file called gunicorn.conf.py at the root of your Django project and add the following config:
    accesslog = '-'
    access_log_format = '%(t)s [%({x-forwarded-for}i)s %(M)sms] "%(m)s %(U)s?%(q)s" %(s)s %(b)s'
    preload_app = True
    timeout = 60
    bind = '0.0.0.0:8000'
    workers = 1
    

We wont go into the details of what each of the above configuration parameter does, but you can check out the official gunicorn documentation to learn more about them.

And that's basically it! If you look closely you'll see that django-admin already added a file called <your project name>/wsgi.py to your main folder which defines the application variable required by WSGI application servers to find your apps code (WSGI is a standard to connect Python programs to the web which is followed by gunicorn and others).

So now you can invoke gunicorn <your-project-name>.wsgi from the root of your project and browse to http://localhost:8000 for an almost production ready version of your app.

If you tried the above, you probably noticed that static files such as CSS, JS and images are missing. That's because Django prefers not to serve them in production. There are several options to deal with this, from configuring a standard web server that sits in front of gunicorn and Django (the option recommended by the official Django docs) to configuring Django to serve them (this is not recommended unless your app is behind a CDN, but if it is, check out the excellent WhiteNoise package).

We'll cover how to set up a standard web server in another post, but before let's get our application server connected to the internet.

Uploading to the Cloud

The main point we're trying to make in this post is that rolling your own servers is not that difficult and can be very cost effective in the long run. Therefore, we'll go with the standard approach in the late 2000: get a virtual private server, install everything needed and keep an eye on it to check if it fails.

Sign up for an account on one of the many VPS providers (Hetzner's our favorite) and spin up the cheapest Linux machine (virtual private server, that is) you can find. Anything with 1 core and at least 512MB of ran will be enough for your first app, and these go for around 5€ per month at most cloud providers. A word of caution though: I'd recommend choosing an option which includes IPv4 connectivity instead of just IPv6, even if you have to pay ~50 cents more a month. Setting up IPv6 connectivity is still quite complex, more so in an IPv6-only environment (some package managers are not even reachable via the IPv6 network!).

If you are not sure which flavor of Linux to pick I'd recommend going for Debian or Ubuntu (one of the LTS versions). These are relatively secure by default and I feel their package manager already includes most of the dependencies you could need in the standard repository. Also, remember to follow standard server hardening procedures just after spinning up your server (I recommend setting up a public key for SSH authentication and configuring a firewall. Both of these can be done from the control panel on most cloud providers so that you don't need to use the terminal to configure them.)

Next, you'll need to install Python and PIP, and SQLite (or the any other database you plan on using). In Debian / Ubuntu-based flavors of Linux this boils down to running on your VPS:

apt-get update
apt-get install python3 sqlite3

Once this is done, you just upload your app, for instance with scp (this command should be run from your local machine):

scp ~/path/to/your/app root@remote-ip:~/app

Then install dependencies by running (again from the VPS shell):

cd ~/app
pip install -r requirements.txt

And finally start your app with gunicorn <your-project-name>.wsgi from the root of your Django project on the VPS shell.

Yay! Now your app should be available at http://<your-vps-ip-address>:8000/.

Closing thoughts

This approach is a little more involved than using a Platform as a Service like Google App Engine or DigitalOcean's App Platform, but at least you'll be learning about the actual systems that power everything instead of the hundreds of terms and abstractions in those proprietary systems. In other words, these skills are universal and transferable, while switching from one cloud platform to another essentially means relearning everything from scratch.

Also, you might get some errors along the way but Django does a pretty good job at describing them and even hinting some solutions. Since the technology we've described in this post is very well-established, anything you encounter can be Googled and the first result to come up will solve it 95% of the time.

Finally, please do check out the excellent Django documentation, especially the part on securing your application for production and the deployment checklist.

Thanks for reading!

Ready to bring your vision to life?

Get in touch