Django Heroku Tutorial Series:
- Heroku vs AWS Which is Best for Your Django project
- How to deploy Django project to Heroku using Docker
- How to deploy Python project to Heroku in Gitlab CI
- How to use Heroku Pipeline
- Heroku Logs Tutorial
- How to monitor Heroku Postgres using heroku-pg-extras
Django Dokku Tutorial Series:
Introduction
Dokku is a PaaS implementation built on docker, it can give you a Heroku-like environment
which is also open source and free.
You can use it on AWS EC2 or VPS such as Digital Ocean to save your time on many DevOps work.
In this Dokku tutorial, I will talk about how to deploy Django project with Dokku, it would use Postgres db and Amazon S3 to store data and media files.
After reading this tutorial, you will get:
- How to install Dokku, config Dokku server
- How to deploy Django project to Dokku
- How to install, config Dokku app and plugins
The source code of this post can be found on Github, please give it star if it helps you.
Generate SSH Key on Local
If you already have SSH key, you can skip this section.
# change to your email address
(local)$ ssh-keygen -t ed25519 -C "[email protected]"
- When you're prompted to "Enter a file in which to save the key," press Enter. This accepts the default file location.
- At the prompt, type a secure passphrase
If you meet problem, please check Generating a new SSH key to learn more.
/Users/michaelyin/.ssh
├── id_ed25519 # new
├── id_ed25519.pub # new
id_ed25519
is private key, you should not share it to other people.id_ed25519.pub
is the public key.
Create New User
After we SSH to our server as root, let's create normal user first.
(root)-# adduser michaelyin
enter some long and complex passwd
Add user to sudo
group
(root)-# usermod -a -G sudo michaelyin
(root)-# groups michaelyin
michaelyin : michaelyin sudo
Make sudo without passwd, note that this setting is not recommended if the vps requires more safety
(root)-# visudo
# only edit this line
%sudo ALL=NOPASSWD: ALL
Now we can change to new user
(root)-# su michaelyin
michaelyin@vps:/root$
'$' denote a normal user prompt
Improve SSH Experience
# make sure we login as normal user instad of root
(server)$ whoami
michaelyin
(server)$ cd ~
(server)$ mkdir .ssh && touch .ssh/authorized_keys
(server)$ chmod 700 .ssh && chmod 600 .ssh/*
Now on local, upload the ssh public key to the vps
(local)$ cat ~/.ssh/id_ed25519.pub | ssh [email protected] "cat >> ~/.ssh/authorized_keys"
Now on local, we can login ssh [email protected]
without typing the complex password, which is convenient.
Please add code below to your ~/.ssh/config
on local.
Host dokku_server
RequestTTY yes
User michaelyin
Hostname 138.197.208.68
Port 22
ServerAliveInterval 60
ServerAliveCountMax 60
Now we can use ssh dokku_server
instead of ssh [email protected]
to login to your server
Install Dokku
We can get the install script on the Dokku release page
Let's SSH to our server and then use code below to install Dokku
# Please use ssh to login to your server
(server)$ whoami
michaelyin
(server)$ cd
(server)$ wget https://raw.githubusercontent.com/dokku/dokku/v0.26.5/bootstrap.sh
(server)$ sudo DOKKU_TAG=v0.26.5 bash bootstrap.sh
The script would also install dependencies and Dokku on the server.
Setup Dokku
After we install Dokku, we will see something like this in the terminal
! Setup a user's ssh key for deployment by passing in the public ssh key as shown:
echo 'CONTENTS_OF_ID_RSA_PUB_FILE' | sudo dokku ssh-keys:add admin
Let's add our public SSH to the Dokku.
# run this command on local
# need root password
(local)$ cat ~/.ssh/id_ed25519.pub | ssh root@dokku_server dokku ssh-keys:add admin
# let's check on server
$ sudo dokku ssh-keys:list
SHA256:XXXXXX NAME="admin" SSHCOMMAND_ALLOWED_KEYS="no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding"
Config Dokku App
Next, let's create and config our Dokku app.
Please note that, the dokku project has name django-dokku-example
.
Create Dokku app
$ sudo dokku apps:create django-dokku-example
-----> Creating django-dokku-example...
Create Postgres DB
Here we put the Postgres db on our server, but you can also use 3-party DB server like Amazon RDS if you like.
Dokku has many plugins and here we use postgres plugin
to help us.
# install the plugin
$ sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git
# let's create db, I add suffix '-db'
# we can even use --image-version to specify postgres version
$ sudo dokku postgres:create django-dokku-example-db --image "postgres" --image-version "14.1"
Waiting for container to be ready
Creating container database
Securing connection to database
=====> Postgres container created: django-dokku-example-db
=====> django-dokku-example-db postgres service information
Config dir: /var/lib/dokku/services/postgres/django-dokku-example-db/data
Config options:
Data dir: /var/lib/dokku/services/postgres/django-dokku-example-db/data
Dsn: postgres://postgres:a24e71a3f0ca3814ae6be809921b0313@dokku-postgres-django-dokku-example-db:5432/django-dokku-example-db
Exposed ports: -
Id: 4919a29ec4820fbf758482b7d91d53ae8fa0bcc27e9ea9bb1bbc6cc0895ca43b
Internal ip: 172.17.0.2
Links: -
Service root: /var/lib/dokku/services/postgres/django-dokku-example-db
Status: running
Version: postgres:14.1
Now link the db to your dokku app, and it would add a new env varialbe DATABASE_URL
to the dokku app.
$ sudo dokku postgres:link django-dokku-example-db django-dokku-example
-----> Setting config vars
DATABASE_URL: postgres://postgres:a24e71a3f0ca3814ae6be809921b0313@dokku-postgres-django-dokku-example-db:5432/django_dokku_example_db
-----> Restarting app django-dokku-example
! App image (dokku/django-dokku-example:latest) not found
Let's print out the env of our Dokku app.
$ sudo dokku config:show django-dokku-example
=====> django-dokku-example env vars
DATABASE_URL: postgres://postgres:a24e71a3f0ca3814ae6be809921b0313@dokku-postgres-django-dokku-example-db:5432/django_dokku_example_db
Config Amazon s3
Unlike Heroku, Dokku also support store media files on local disk, you can check Dokku Persistent Storage for more detail
- If you have no Amazon service account, please go to Amazon S3 and click the
Get started with Amazon S3
to signup. - Login AWS Management Console
- In the top right, click your company name and then click
My Security Credentials
- Click the
Access Keys
section Create New Access Key
, please copy theAMAZON_S3_KEY
andAMAZON_S3_SECRET
to notebook.
Next, we start to create Amazon bucket on S3 Management Console, please copy Bucket name
to notebook.
Bucket
in Amazon S3 is like top-level container, every site should have its own bucket
, and the bucket name are unique across all Amazon s3, and the url of the media files have domain like {bucket_name}.s3.amazonaws.com
.
Now we add AWS_STORAGE_BUCKET_NAME
, AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
, AWS_S3_REGION_NAME
env variable to our Dokku app.
Please use the values from your notebook.
$ sudo dokku config:set --no-restart django-dokku-example AWS_STORAGE_BUCKET_NAME='CHANGE ME'
$ sudo dokku config:set --no-restart django-dokku-example AWS_ACCESS_KEY_ID='CHANGE ME'
$ sudo dokku config:set --no-restart django-dokku-example AWS_SECRET_ACCESS_KEY='CHANGE ME'
$ sudo dokku config:set --no-restart django-dokku-example AWS_S3_REGION_NAME='CHANGE ME'
By default, Dokku would restart the app if you changed env variable, here we use --no-restart
to tell Dokku not restart it.
Config other env variable
There are still some env variables we need to config, please use dokku config:set --no-restart django-dokku-example
to add them to Dokku app.
DJANGO_ALLOWED_HOSTS: *
DJANGO_SECRET_KEY: xxxxxxxxx
DJANGO_SETTINGS_MODULE: django_dokku_example.settings
DJANGO_DEBUG: False
Config domain
Next, we start to config domain.
$ sudo dokku domains:add django-dokku-example dokku.accordbox.com
-----> Added dokku.accordbox.com to django-dokku-example
! No web listeners specified for django-dokku-example
Here we add to dokku.accordbox.com
to our Dokku app django-dokku-example
, we can add more than one domain to Dokku app.
Config Django project
Now the Dokku app env is ready, before pushing code to Dokku server, let's prepare our Django project.
You would also see we need to add some config files, which are very similar with Heroku's config file, Dokku would scan and read them to decide some deployment workflow.
Environment
First, let's add django-environ
to requirements.txt
.
django-environ==0.8.1
And then, let's update Django settings django_dokku_example/settings.py
to use django-environ
to read the env.
import environ # new
from pathlib import Path
env = environ.Env() # new
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('DJANGO_SECRET_KEY', default='django-insecure-$lko+#jpt#ehi5=ms9(6s%&6fsg%r2ag2xu_2zj1ibsj$pckud')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool("DJANGO_DEBUG", True)
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[])
Notes:
- Here we use
django-environ
to setSECRET_KEY
,DEBUG
andALLOWED_HOSTS
from the Environment variable. - And we also set
default
value to make it work in development without extra Env.
$ ./manage.py migrate
$ ./manage.py runserver
Now, please check on http://127.0.0.1:8000/ to make sure everything is working.
Static files
To serve static assets, we need to use a 3-party package. whitenoise
.
Add whitenoise
to requirements.txt
whitenoise==5.3.0
Update django_dokku_example/settings.py
import os # new
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # new
# ...
]
STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static') # new
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" # new
That is all the config you need.
Store media in Amazon s3
update requirements.txt
.
boto3==1.16.56
django-storages==1.11.1
Add storages
to INSTALLED_APPS
in django_dokku_example/settings.py
And then update django_dokku_example/settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # new
MEDIA_URL = '/media/' # new
if 'AWS_STORAGE_BUCKET_NAME' in env: # new
AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME')
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY')
AWS_S3_REGION_NAME = env('AWS_S3_REGION_NAME')
AWS_DEFAULT_ACL = 'public-read'
AWS_S3_FILE_OVERWRITE = False
MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_S3_FILE_OVERWRITE
please set it to False, so this can let the storage handleduplicate filenames
problem. (I do not understand why so many blog posts did not mention this)
Please note the AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
is for AWS admin
, for better security, you should create AWS IAM
user and then grant S3 permissions. You can check Bucket owner granting its users bucket permissions to learn more.
Postgres DB
Update django_dokku_example/settings.py
if "DATABASE_URL" in env:
DATABASES['default'] = env.db('DATABASE_URL')
DATABASES["default"]["ATOMIC_REQUESTS"] = True
Here env.db
would convert DATABASE_URL
to Django db connection dict for us.
Do not forget to add psycopg2-binary
to requirements.txt
.
psycopg2-binary==2.9.2
runtime.txt
To specify a Python runtime, add a runtime.txt file to your app’s root directory that declares the exact version number to use
python-3.10.0
Procfile
This file exists in root directory of our project. It contains entry command of our service.
web: gunicorn django_dokku_example.wsgi:application
release: django-admin migrate --noinput
Here web
means the web service, Dokku would use the command to run for our web service.
And release
is the command which would be run in release stage, here we use the command to migrate our database in release stage.
If you used Celery worker in your project, you can add worker: ...
to do that.
Add gunicorn
to requirements.txt
gunicorn==20.1.0
requirements.txt or Pipfile
Please make sure your project has requirements.txt
or Pipfile
so Dokku can check if it is Python project and would install dependencies when deploying.
Deploy project
Please commit code first and then keep reading.
First we add a remote branch dokku
to our Git repo.
$ git remote add dokku dokku@dokku_server:django-dokku-example
Notes:
dokku_server
here can also be the ip address of the server (dokku_server
is the hostname we config in~/.ssh/config
),django-dokku-example
is the dokku app we just created on our server.
Then we push our code to remote master
branch
$ git push dokku master:master
Below is the output, Dokku would check language we use and download relevant Heroku buildpack to deploy the project. Which is very cool!
Counting objects: 177, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (162/162), done.
Writing objects: 100% (177/177), 49.38 KiB | 3.53 MiB/s, done.
Total 177 (delta 45), reused 0 (delta 0)
=====> Application deployed:
http://dokku.accordbox.com
To dokku_server:django-dokku-example
* [new branch] master -> master
If the push command did not raise error, you should see something like this and now our app is live on http://dokku.accordbox.com
Let's check http://dokku.accordbox.com/admin to make sure Django app is running without issue.
Setup SSL
Let's make https
work for our site.
(server)$ sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
(server)$ sudo dokku config:set --no-restart --global [email protected]
(server)$ sudo dokku letsencrypt:enable django-dokku-example
(server)# this would setup cron job to update letsencrypt certificate
(server)$ sudo dokku letsencrypt:cron-job --add
Please replace DOKKU_LETSENCRYPT_EMAIL
with your email.
After https
can work on your site, you can setup http->https
to improve security, Django SECURE_SSL_REDIRECT
can help.
Some Advanced issues
Run Dokku command without sudo
It is annoying to make everything dokku command have sudo
From the Dokku doc
(server)$ sudo visudo /etc/sudoers
# add following line
%dokku ALL=(ALL:ALL) NOPASSWD:SETENV: /usr/bin/dokku
This means any user in dokku
group can run /usr/bin/dokku
without password.
Next, let's add user michaelyin
to the dokku
group
$ sudo usermod -a -G dokku michaelyin
# exit and login again
$ groups
michaelyin sudo dokku
As you can see, now michaelyin
is in dokku
group, and we can run dokku command without sudo
$ dokku apps:list
=====> My Apps
django-dokku-example
Troubleshoot
It is common if your Django projects return 500 error page in some cases, so how to troubleshoot?
- I strongly recommend to use https://sentry.io/ to help you solve this, the free plan is enough for personal projects.
- Or you can check the running logs using this command
dokku logs django-dokku-example
Customize Nginx
Sometimes, we need to customize our Nginx settings.
For example, the default Nginx settings has small client_max_body_size
, so I need to change it to make upload feature work in my Django project.
Dokku
generate nginx config from nginx.conf.sigil
, you can first download it from Dokku repo (better download from specific Git tag you use).
And then you can modify your settings.
You can add that file to root directory of your project and Dokku would use it to generate new nginx config.
- You can check Doc: Customizing the nginx configuration
- In the new version, you can set
client_max_body_size
withdokku
command, https://dokku.com/docs/configuration/nginx/#specifying-a-custom-client_max_body_size
Zero Downtime Deploy
When Dokku deploy, it would start container which has the latest code and then wait for 10 secs to make sure the service is ok to run.
To decrease the time, we can add CHECKS
file and Dokku would use that file to check if our web server is ok to serve.
/admin Django
It tells Dokku to visit /admin
url and check if the response contains Django
-----> Deploying web (count=1)
Attempting pre-flight checks (web.1)
CHECKS expected result: http://localhost/admin => "Django" (web.1)
Attempt 1/5. Waiting for 5 seconds (web.1)
All checks successful (web.1)
Run Django command
If you want to run some command on web server, for example, run Django shell, you can use command in this way
(server)$ dokku run django-dokku-example python manage.py shell
Here Dokku would create a new container for you to use and this can avoid some risky operation.
The container is deleted when you exit.
Dokku Client
If you want to run dokku on local without SSH command, you can check Dokku official client
After setting it up, we can enter Django shell on the server:
(local)$ DOKKU_HOST=dokku_server dokku run django-dokku-example python manage.py shell
Setup Cron job
If you want to setup cron jobs for your project, it is better to do it on host machine.
# we setup crontjob as dokku user
(server)$ sudo crontab -u dokku -e
@daily dokku run django-dokku-example python manage.py clearsessions
As you can see, we setup clearsessions
job to run each day and it helps us clean out expired sessions in db.
Conclusion
In this Dokku tutorial, I showed you how to deploy Django project to Dokku.
The source code of this post can be found on Github, please give it star if it helps you.
Django Heroku Tutorial Series:
- Heroku vs AWS Which is Best for Your Django project
- How to deploy Django project to Heroku using Docker
- How to deploy Python project to Heroku in Gitlab CI
- How to use Heroku Pipeline
- Heroku Logs Tutorial
- How to monitor Heroku Postgres using heroku-pg-extras
Django Dokku Tutorial Series: