How to Deploy Django Applications on Heroku
Published: 2022-05-19 18:23:36

### Install Heroku CLI
[Sign Up](https://signup.heroku.com/login) to Heroku

After installing the Heroku CLI, open a terminal and login to your account:

```
$ heroku login
heroku: Press any key to open up the browser to login or q to exit: 
heroku: Waiting for login... ⣻
```
You're redirected to your browser and you login to Heroku with Heroku CLI and get a success message on your terminal

### Creating App
First make sure you are in the root directory of the repository you want to deploy

Next create the heroku app from the terminal:

```
heroku create <your-app>
```


### Preparing the Application
#### Assumptions

- You're familiar with the basics of Django. _eg. concept of apps, settings, urls, basics of databases._
- You have a Django application that you want to deploy to Heroku.
- You are familiar with virtual environments
- Your deployment Database is **PostgreSQL**

You will have to install several packages that will come in handy:
- django-heroku `pip install django-heroku`
- gunicorn `pip install gunicorn`
- decouple `pip install python-decouple`
- DATABASE_URL `pip install dj-database-url`
- whitenoise `pip install whitenoise`

You will then add the following files in your application

- Add a `Procfile` in the project root;
- Add `requirements.txt` file with all the requirements in the project root;
- Add `requirements.txt` with `pip freeze > requirements.txt`
- A `runtime.txt` to specify the correct Python version in the project root;
- Configure `whitenoise` to serve static files.

Now we go through each one of them

##### <u>Procfile</u>
Heroku apps include a Procfile that specifies the commands that are executed by the app’s dynos.
Create a file named Procfile in the project root with the following content:

```
web: gunicorn your_project_name.wsgi
```

##### <u>runtime.txt</u>
This file contains the python version you are using for heroku to use, create `runtime.txt` in your project root and add your python version in the following format:

```
python-3.8.12
```
You can specify which runtime to use for your app. List of [Heroku Runtimes](https://devcenter.heroku.com/articles/python-runtimes)

### Configuring Whitenoise: Django Static Files settings
It turns out django does not support serving static files in production. However, WhiteNoise project can integrate into your Django application, and was designed with exactly this purpose in mind.

Lets first configure static related parameter in `settings.py`

```
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'

# Extra places for collectstatic to find static files.
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
```
Then add this line of code in the `middleware section`

```
MIDDLEWARE_CLASSES = (
    # Simplified static file serving.
    # https://warehouse.python.org/project/whitenoise/
    'whitenoise.middleware.WhiteNoiseMiddleware',
    ...
```
Add the following setting to settings.py in the static files section to enable gzip functionality.

```
# Simplified static file serving.
# https://warehouse.python.org/project/whitenoise/

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

# configuring the location for media
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# Configure Django App for Heroku.
django_heroku.settings(locals())
```

### python-decouple and dj-database-url
Python Decouple is a must have app if you are developing with Django. It’s important to keep your application credentials like API Keys, tokens and passwords.
dj-database-url is a simple Django utility that holds your Postgres database url

#### <u>.env</u>
Firts create a `.env` file and add it to `.gitignore `so you don’t commit any sensitive data to your repository. Below is an example of configurations you can add to the `.env` file.

```
# An example, don't share your.env settings
SECRET_KEY='342s(s(!hsjd998sde8$=o4$3m!(o+kce2^97kp6#ujhi'
DEBUG=True
DB_NAME='db_name'
DB_USER='user'
DB_PASSWORD='db_password'
DB_HOST='127.0.0.1'
MODE='dev'
ALLOWED_HOSTS='<app name in heroku>.herokuapp.com'
DISABLE_COLLECTSTATIC=1
```
We then edit settings.py to enable decouple to use the .env configurations.

```
import os
import django_heroku
import dj_database_url
from decouple import config,Csv

MODE=config("MODE", default="dev")
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
# development
if config('MODE')=="dev":
   DATABASES = {
       'default': {
           'ENGINE': 'django.db.backends.postgresql_psycopg2',
           'NAME': config('DB_NAME'),
           'USER': config('DB_USER'),
           'PASSWORD': config('DB_PASSWORD'),
           'HOST': config('DB_HOST'),
           'PORT': '',
       }
       
   }
# production
else:
   DATABASES = {
       'default': dj_database_url.config(
           default=config('DATABASE_URL')
       )
   }

db_from_env = dj_database_url.config(conn_max_age=500)
DATABASES['default'].update(db_from_env)

ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())
```

### Deploying the app to Heroku
Create a postgres addon to your Heroku app
```
heroku addons:create heroku-postgresql:hobby-dev
```
Next we log in to [Heroku dashboard ](https://dashboard.heroku.com/apps) to access our app and configure it

![Heroku dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1t81f7kp8snf4a6557or.png)
Add all your configurations in `.env` file directly to Heroku by running this command.

```
heroku config:set $(cat .env | sed '/^$/d; /#[[:print:]]*$/d')
```
Remember to first set DEBUG to False and confirm that you have added all the configuration variables needed. _Click on the Settings menu and then on the button Reveal Config Vars_

#### Pushing to Heroku
Confirm that your app is running as expected before pushing. Runtime errors will cause deployment to fail so make sure you have no bugs and you have all the following; `Procfile`, `requirements.txt` with all required packages and `runtime.txt`.

Commit all the changes we have made and then:
```
git push heroku master
```
- If you are using main as the branch, change master to main

If you did everything correctly then the deployment should be done after a while with an output like this

```
Enumerating objects: 94, done.
Counting objects: 100% (94/94), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (84/84), done.
Writing objects: 100% (94/94), 3.35 MiB | 630.00 KiB/s, done.
Total 94 (delta 24), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Python app detected
remote: -----> Installing python-3.6.6
remote: -----> Installing pip
remote: -----> Installing requirements with pip
remote:        Collecting config==0.3.9 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 1))
remote:          Downloading https://files.pythonhosted.org/packages/0a/46/186ac016f3175211ec9bb4208579bc6dc9dd7dc882790d9f281533b83b0f/config-0.3.9.tar.gz
remote:        Collecting dj-database-url==0.5.0 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 2))
remote:          Downloading https://files.pythonhosted.org/packages/d4/a6/4b8578c1848690d0c307c7c0596af2077536c9ef2a04d42b00fabaa7e49d/dj_database_url-0.5.0-py2.py3-none-any.whl
remote:        Collecting Django==1.11 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 3))
remote:          Downloading https://files.pythonhosted.org/packages/47/a6/078ebcbd49b19e22fd560a2348cfc5cec9e5dcfe3c4fad8e64c9865135bb/Django-1.11-py2.py3-none-any.whl (6.9MB)
remote:        Collecting django-bootstrap3==10.0.1 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 4))
remote:          Downloading https://files.pythonhosted.org/packages/18/a8/f12d8491155c7f237084b883b8600faf722e3a46e54f17a25103b0fb9641/django-bootstrap3-10.0.1.tar.gz (40kB)
remote:        Collecting django-heroku==0.3.1 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 5))
remote:          Downloading https://files.pythonhosted.org/packages/59/af/5475a876c5addd5a3494db47d9f7be93cc14d3a7603542b194572791b6c6/django_heroku-0.3.1-py2.py3-none-any.whl
remote:        Collecting gunicorn==19.9.0 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 6))
remote:          Downloading https://files.pythonhosted.org/packages/8c/da/b8dd8deb741bff556db53902d4706774c8e1e67265f69528c14c003644e6/gunicorn-19.9.0-py2.py3-none-any.whl (112kB)
remote:        Collecting Pillow==5.2.0 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 7))
remote:          Downloading https://files.pythonhosted.org/packages/d1/24/f53ff6b61b3d728b90934bddb4f03f8ab584a7f49299bf3bde56e2952612/Pillow-5.2.0-cp36-cp36m-manylinux1_x86_64.whl (2.0MB)
remote:        Collecting psycopg2==2.7.5 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 8))
remote:          Downloading https://files.pythonhosted.org/packages/5e/d0/9e2b3ed43001ebed45caf56d5bb9d44ed3ebd68e12b87845bfa7bcd46250/psycopg2-2.7.5-cp36-cp36m-manylinux1_x86_64.whl (2.7MB)
remote:        Collecting python-decouple==3.1 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 9))
remote:          Downloading https://files.pythonhosted.org/packages/9b/99/ddfbb6362af4ee239a012716b1371aa6d316ff1b9db705bfb182fbc4780f/python-decouple-3.1.tar.gz
remote:        Collecting pytz==2018.5 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 10))
remote:          Downloading https://files.pythonhosted.org/packages/30/4e/27c34b62430286c6d59177a0842ed90dc789ce5d1ed740887653b898779a/pytz-2018.5-py2.py3-none-any.whl (510kB)
remote:        Collecting whitenoise==3.3.1 (from -r /tmp/build_19aebf8f25d534a39e73b13219af9927/requirements.txt (line 11))
remote:          Downloading https://files.pythonhosted.org/packages/0c/58/0f309a821b9161d0e3a73336a187d1541c2127aff7fdf3bf7293f9979d1d/whitenoise-3.3.1-py2.py3-none-any.whl
remote:        Installing collected packages: config, dj-database-url, pytz, Django, django-bootstrap3, whitenoise, psycopg2, django-heroku, gunicorn, Pillow, python-decouple
remote:          Running setup.py install for config: started
remote:            Running setup.py install for config: finished with status 'done'
remote:          Running setup.py install for django-bootstrap3: started
remote:            Running setup.py install for django-bootstrap3: finished with status 'done'
remote:          Running setup.py install for python-decouple: started
remote:            Running setup.py install for python-decouple: finished with status 'done'
remote:        Successfully installed Django-1.11 Pillow-5.2.0 config-0.3.9 dj-database-url-0.5.0 django-bootstrap3-10.0.1 django-heroku-0.3.1 gunicorn-19.9.0 psycopg2-2.7.5 python-decouple-3.1 pytz-2018.5 whitenoise-3.3.1
remote: 
remote: -----> Discovering process types
remote: 
remote: -----> Compressing...
remote:        Done: 56.3M
remote: -----> Launching...
remote:        Released v6
remote:        https://mtr1bune.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/mtr1bune.git
 * [new branch]      master -> master
```

### Run Migrations

```
heroku run python manage.py migrate
```
If you wish to push your postgres database data to Heroku then run

```
heroku pg:reset
heroku pg:push <The name of the db in the local psql> DATABASE_URL --app <heroku-app>
```
You can the open the app in your browse.

#### Last comment
Don't beat yourself up when you find it challenging to memorize language or library syntax. They have documentation for a reason, so feel free to reference it. The syntax will stick to your memory on frequent use.💯✍️

![tweet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/emumt6zkh7bq31iduycm.png)