Django deployment with Dokku
After recently having migrated our previous Django deployment stack to Dokku, we are sharing our experience in this post.
Dokku
Dokku is Platform As A Service (PaaS) solution you can host on your own server. It is similar to Heroku but self-hosted. They share the same buildpacks used to build applications.
Installation is pretty straightforward and well explained on the official documentation. In this tutorial we assume you have a working Dokku installation and we just skip how on to deploy a Django app.
Create the app
On your server where Dokku is installed, create an app called myapp :
$ dokku apps:create *myapp*
Install the PostgreSQL plugin:
$ sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
A plugin also exists for MySQL if you prefer this database.
Create a new database and link it to your app:
$ dokku postgres:create myapp_database
$ dokku postgres:link myapp_database myapp
Django
We will assume you already have a Django projext and we will cover what you must add in order to deploy to Dokku.
Building a Python app requires a requirements.txt file in order to install dependencies. At the very least it must contains Django, gunicorn and dj-database-url:
Django==2.2.1
gunicorn==19.7.1
dj-database-url==0.5.0
gunicorn is the WSGI server responsible to handle HTTP requests and forward them to Django. It is only necessary in production, you can skip it in your local requirements.txt.
When you provisioned a database earlier Dokku automatically added an environment variable named DATABASE_URL which contains the necessary information to connect the database: the host, port, credentials and database name. It contains a string like this one :
postgres://postgres:6f436ebbf48563fd68d7eac876c20736@dokku-postgres-myapp-database:5432/myapp_database
You must parse this string in order to configure the DATABASES in your Django settings.py. dj-database-url does this for you:
import os
import dj_database_url
DATABASES = {
'default': dj_database_url.parse(os.environ.get('DATABASE_URL')),
}
After that, we need to tell Dokku how to run our application. Add a file named Procfile to the root of your project. As mentioned before we will launch a WSGI server with Gunicorn :
web: gunicorn wsgi:application
Optionally (but recommended) you can set your python version in a runtime.txt file:
python-3.7.3
You're all set, you can now deploy using git:
$ git remote add dokku dokku@your_dokku_server_hostname:myapp
$ git push dokku master
Static files
Every Django project comes with static files which must be served by one way or an other. Official documentation recommends setting up a web server or using a CDN like Amazon S3. We used a third option with the great whitenoise project . Please refer to their FAQ to find out why is is not a so bad idea to serve static files with Python.
Add whitenoise to your requirements.txt
Django==2.2.1
gunicorn==19.7.1
whitenoise==4.1.2
and the middleware in your app settings.py:
MIDDLEWARE = [
# 'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...
]
Media storage
Media files are those uploaded via the Django admin or by your users. As your app is built into a Docker container and Docker containers are ephemeral any files you add to the container is lost after the next deployment. Hence the recommended way to host your media files is to use Amazon S3.
In our case, as we like to host everything ourselves we took advantage of Dokku persistent storage capability to keep media files on our server.
On your Dokku server, create a directory and add a mount point for your app:
$ mkdir /var/lib/dokku/data/storage/myapp
$ dokku storage:mount myapp /var/lib/dokku/data/storage/myapp:/storage
Then you need to override the nginx configuration to serve files in this directory: create a file /home/dokku/myapp/nginx.conf.d/media.conf with the following content:
location /media {
alias /var/lib/dokku/data/storage/myapp;
}
And finally in your project settings.py configure the MEDIA related stuff:
MEDIA_ROOT = '/storage'
MEDIA_URL = '/media/'
Commit, push to dokku again, and voila! You are deploying a Django app on your own PaaS!