Jun 14, 2017

How to Deploy a Django Application on Heroku?

Vladimir Kalyuzhny

1. Getting started on Heroku

First thing you need to do is to create an account. Heroku offers four pricing plans: Free, Hobby, Standard, Performance. In this tutorial we are going to use the Free-plan. It’s necessary to remember that this plan has certain limitations you need to peruse with before you deploy django app to heroku.

Creating an account on Heroku

If you already have an account on Heroku, skip this step. To create an account you need to fill out a registration form by clicking on the link. It shouldn’t take you more than one minute neither bring any difficulties. The only thing I want to pay attention to is that don’t forget to choose Python at “Primary Development Language” line. After filling the form confirm your email and let’s get down to business.

Installing the Heroku CLI

We’ll need Heroku CLI. In this tutorial we will quite often use CLI to interact with Heroku.

After the installation process is finished, open console and enter the username and password of your account.

$ heroku login
Enter your Heroku credentials:
Email: [email protected]
Password: **
Logged in as [email protected]

Creating Heroku App

$ heroku create django-heroku-blog

Creating ⬢ django-heroku-blog... done |

Connecting PostgreSQL

$ heroku addons:create heroku-postgresql:hobby-dev

Creating heroku-postgresql:hobby-dev on ⬢ django-heroku-blog... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created postgresql-acute-85745 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation

2. Configuring django apps for heroku

For this article I wrote a simple Django application. First, we need to do some adjustments to our app before starting to deploy django to heroku.


requirements.txt is a file which contains a list of all necessary app packages. Create this file in the root folder with such content inside:



Procfile describes command need to be executed for app launch.

web: gunicorn heroku_blog.wsgi --log-file -


It is the file in which we specify Python version


Separating Django settings

We need to get three files with the following settings:

  • settings/ - basic configuration
  • settings/ - production configuration (this particular file will be used on Heroku)
  • settings/ - local configuration

Such separation of settings will become necessary when our app starts to grow. You will find link to the code of these files at the end of this article.

Setting up static files

For the correct display of static files we need to modify и

# settings/
STATIC_ROOT = os.path.join(BASE_DIR, 'assets')
STATIC_URL = '/static/'

STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'

    os.path.join(BASE_DIR, 'static'),
import os
from django.core.wsgi import get_wsgi_application
from whitenoise.django import DjangoWhiteNoise

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "heroku_blog.settings.production")

application = get_wsgi_application()
application = DjangoWhiteNoise(application)

Database Configuration

In the previous step we’ve created PostgreSQL database. Let’s connect it to our app. Go to “Settings” and copy the entire DATABASE_URL contents.

After that, paste contents into settings/, you must end up with this:

# settings/
from .base import 
import dj_database_url

ENVIRONMENT = 'production'
DEBUG = False
DATABASES['default'] = dj_database_url.config(

Local launch

Preliminary configuration of our app is done. Before we start to push django on heroku, let’s check how our app is displayed locally. For this we have heroku local web command.

$ heroku local web

12:44:01 web.1   |  [2017-05-12 12:44:01 +0300] [31988] [INFO] Starting gunicorn 19.7.1
12:44:01 web.1   |  [2017-05-12 12:44:01 +0300] [31988] [INFO] Listening at: (31988)
12:44:01 web.1   |  [2017-05-12 12:44:01 +0300] [31988] [INFO] Using worker: sync
12:44:01 web.1   |  [2017-05-12 12:44:01 +0300] [31992] [INFO] Booting worker with pid: 31992

If no mistakes were returned and you don’t see any of them in the console, then everything went well and you can proceed to the next step.

3. Deploying django to heroku

Logging into Heroku account

heroku login

We'll push code and heroku should deploy the app automatically.

$ git push heroku master

Подсчет объектов: 63, готово.
Delta compression using up to 4 threads.
Сжатие объектов: 100% (55/55), готово.
Запись объектов: 100% (63/63), 294.76 KiB | 0 bytes/s, готово.
Total 63 (delta 9), reused 4 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: -----> Python app detected
remote: -----> Installing python-3.6.1
remote: -----> Installing pip
remote: -----> Installing requirements with pip
remote:        Collecting Django===1.11.1 (from -r /tmp/build_075c2ff1315d45c636d7ab6ff1d6d586/requirements.txt (line 1))
remote:          Downloading Django-1.11.1-py2.py3-none-any.whl (6.9MB)
remote:        Collecting psycopg2==2.7.1 (from -r /tmp/build_075c2ff1315d45c636d7ab6ff1d6d586/requirements.txt (line 2))
remote:          Downloading psycopg2-2.7.1-cp36-cp36m-manylinux1_x86_64.whl (2.7MB)
remote:        Collecting gunicorn==19.7.1 (from -r /tmp/build_075c2ff1315d45c636d7ab6ff1d6d586/requirements.txt (line 3))
remote:          Downloading gunicorn-19.7.1-py2.py3-none-any.whl (111kB)
remote:        Collecting whitenoise==3.3.0 (from -r /tmp/build_075c2ff1315d45c636d7ab6ff1d6d586/requirements.txt (line 4))
remote:          Downloading whitenoise-3.3.0-py2.py3-none-any.whl
remote:        Collecting dj-database-url==0.4.2 (from -r /tmp/build_075c2ff1315d45c636d7ab6ff1d6d586/requirements.txt (line 5))
remote:          Downloading dj_database_url-0.4.2-py2.py3-none-any.whl
remote:        Collecting python-decouple==3.0 (from -r /tmp/build_075c2ff1315d45c636d7ab6ff1d6d586/requirements.txt (line 6))
remote:          Downloading python-decouple-3.0.tar.gz
remote:        Collecting pytz (from Django===1.11.1->-r /tmp/build_075c2ff1315d45c636d7ab6ff1d6d586/requirements.txt (line 1))
remote:          Downloading pytz-2017.2-py2.py3-none-any.whl (484kB)
remote:        Installing collected packages: pytz, Django, psycopg2, gunicorn, whitenoise, dj-database-url, python-decouple
remote:          Running install for python-decouple: started
remote:            Running install for python-decouple: finished with status 'done'
remote:        Successfully installed Django-1.11.1 dj-database-url-0.4.2 gunicorn-19.7.1 psycopg2-2.7.1 python-decouple-3.0 pytz-2017.2 whitenoise-3.3.0
remote: -----> $ python collectstatic --noinput
remote:        77 static files copied to '/tmp/build_075c2ff1315d45c636d7ab6ff1d6d586/assets', 103 post-processed.
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote: -----> Compressing...
remote:        Done: 67M
remote: -----> Launching...
remote:        Released v4
remote: deployed to Heroku
remote: Verifying deploy.... done.
 * [new branch]      master -> master

Running migrations

Don’t forget to add --settings=heroku_blog.settings.production, because that’s how system understands that particular file with production-server settings has to be used.

$ heroku run python migrate --settings=heroku_blog.settings.production

Running python migrate --settings=heroku_blog.settings.production on ⬢ django-heroku-blog... up, run.6570 (Free)
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK

Checking the result

After completing all the steps above we get ready-to-go app which can be found at


If you’re receiving errors while deploying an app, you can check what went wrong

$ heroku logs --tail

2017-05-12T12:25:15.000000+00:00 app[api]: Build started by user [email protected]
2017-05-12T12:25:38.517361+00:00 app[api]: Release v8 created by user [email protected]
2017-05-12T12:25:38.517361+00:00 app[api]: Deploy a90e5e3 by user [email protected]
2017-05-12T12:25:38.888015+00:00 heroku[web.1]: Restarting
2017-05-12T12:25:38.888721+00:00 heroku[web.1]: State changed from up to starting
2017-05-12T12:25:15.000000+00:00 app[api]: Build succeeded
2017-05-12T12:25:39.760955+00:00 heroku[web.1]: Stopping all processes with SIGTERM
2017-05-12T12:25:39.778876+00:00 app[web.1]: [2017-05-12 12:25:39 +0000] [9] [INFO] Worker exiting (pid: 9)
2017-05-12T12:25:39.782260+00:00 app[web.1]: [2017-05-12 12:25:39 +0000] [4] [INFO] Handling signal: term
2017-05-12T12:25:39.791042+00:00 app[web.1]: [2017-05-12 12:25:39 +0000] [8] [INFO] Worker exiting (pid: 8)
2017-05-12T12:25:39.983593+00:00 app[web.1]: [2017-05-12 12:25:39 +0000] [4] [INFO] Shutting down: Master
2017-05-12T12:25:40.276436+00:00 heroku[web.1]: Process exited with status 0

4. Django Celery on Heroku

Redis installation on Heroku

$ heroku addons:create heroku-redis:hobby-dev

Creating heroku-redis:hobby-dev on ⬢ django-heroku-blog... free
Your add-on should be available in a few minutes.
! WARNING: Data stored in hobby plans on Heroku Redis are not persisted.
redis-rectangular-25956 is being created in the background. The app will restart when complete...
Use heroku addons:info redis-rectangular-25956 to check creation progress
Use heroku addons:docs heroku-redis to view documentation

Updating the requirements.txt file

Add two new packages:



Take REDIS_URL value from Settings and paste it to settings/ After that, the Celery settings should look like that:

# settings/


For Celery file has to be created in which celery initialization will take place. More details on how to set up celery in Django you can find here Using Celery with Django.

from future import absolute_import, unicode_literals
import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'heroku_blog.settings.base')
app = Celery('heroku_blog')
app.config_from_object('django.conf:settings', namespace='CELERY')

Let’s add celery test-task. This task was taken from the documentation and what it simply does is it returns a sum of two numbers.

from celery import shared_task

def add(x, y):
    return x + y

Updating Procfile

As we have seen before, Procfile contains commands that need to be executed if we want to launch an app. We need to launch celery at the moment of the app launch. To do this we need to update procfile by adding the string worker: celery worker -A heroku_blog -E -l info. As a result, procfile should look like that:

web: gunicorn heroku_blog.wsgi --log-file -
worker: celery worker -A heroku_blog -E -l debug

Deploying Сelery on Heroku

$ git push heroku master


We’re all set! Now let’s double-check if we did all correctly:

Open console and run a command

$ heroku run python shell  --settings=heroku_blog.settings.production
from  blog.tasks import add
add.delay(7, 9)

In the next tab run log output, there should appear statement that the celery task was completed successfully.

$ heroku logs --tail

2017-05-12T12:25:21.601945+00:00 app[worker.1]: [2017-05-12 12:25:21,600: INFO/MainProcess] Received task: blog.tasks.add[ae5ffbd6-a307-4b13-97e8-b50aabd2cc66]
2017-05-12T12:25:22.601934+00:00 app[worker.1]: [2017-05-12 12:25:22,614: INFO/PoolWorker-1] Task blog.tasks.add[ae5ffbd6-a307-4b13-97e8-b50aabd2cc66] succeeded in 0.009866348467767239s: 16

That’s it. The app’s source code you can find here:

More thoughts

Mar 6, 2010ManagementTechnology
Supplementing settings in settings_local

For local project settings, I use old trick with settings_local file:try:from settings_local import \*except ImportError:passSo in we can override variables from I didn't know how to supplement them. For example how to add line to INSTALLED_APPS without copying whole list.Yesterday I finally understood that I can import settings from settings_local:# settings_local.pyfrom settings import \*INSTALLED_APPS += (# ...)

Vladimir Sidorenko
Feb 12, 2020Technology
5 Best Payment Gateways For 2020

We reviewed the best payment gateways in 2020. Here’s our comparison of their features, advantages, and disadvantages.

Vladimir Sidorenko
Aug 27, 2020Technology
5 tips for designing database architecture

Designing database architecture is a challenging task, and it gets even more difficult when your app keeps getting bigger. Here are several tips on how to manage your data structure in a more efficient way.

Yurii Mironov
Sep 23, 2010Technology
OR and AND without django.db.models.Q

I just found out that __or__ and __and__ are defined for QuerySet. This means that to do queries union or intersection, you can do:User.objects.filter(...) | User.objects.filter(...)User.objects.filter(...) & User.objects.filter(...)

Vladimir Sidorenko
Oct 22, 2016Technology
Solr Sharding

When dealing with one of our projects (LookSMI media monitoring platform) we have to handle the huge volume of data – and its quantity is constantly growing. At the same time, we must run quick searches with smart rules. In this article I'll explain how we have achieved required performance.

Rostyslav Stekh
May 9, 2018Technology
How to Generate PDF Files in Python with Xhtml2pdf, WeasyPrint or Unoconv

Programmatic generation of PDF files is a frequent task when developing applications that can export reports, bills, or questionnaires. In this article, we will consider three common tools for creating PDFs, including their installation and converting principles.

Vladimir Sidorenko