CommonLounge Archive

Hands-on Assignment: Deploying to Production Part 2 (Heroku & Git)

May 17, 2018

Now comes the exciting part! We’re going to learn how to deploy our application onto Heroku and have it be accessible by anyone, anywhere in the world. We’ll walk through all the installation instructions required for you to be able to easily run your web application on Heroku and easily push updates.

What is Heroku?

Heroku is a cloud platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud.

Heroku is easy to set up and has a free account so that you can follow along and deploy your Reddit (or any other) application. First, we will need to set up Git, a versioning control system, for our codebase. This will allow us to easily integrate with Heroku.

Let’s dive right into it!

Step 1: Installing Git

These Git instructions are adapted from DjangoGirls and licensed under CC BY-SA 4.0.

Git is a version control system (VCS) that tracks changes to files over time. It maintains a history of every version of your code, allowing you to revisit specific versions whenever you’d like. A bit like the “track changes” feature in Microsoft Word, but much more powerful.

Heroku uses git for its deployments, so let’s go ahead and install it.

Windows

Download Git from here. Choose Windows and select the 32 bit or 64 bit option. If you’re unsure if you should use the 64-bit or 32-bit installer, go here to find out which version of Windows you have.

You can select next on all steps except for the one title Adjusting your PATH environment. On that step, select the bottom option of Use Git and optional Unix tools from the Windows Command Prompt.

Everything else can remain as the default setting.

Don’t forget to restart the command prompt or powershell after the installation has finished successfully.

OS X

Download Git from here and follow the instructions.

Note: If you are running OS X 10.6-10.8, you will need to install this version of git.

Debian or Ubuntu

$ sudo apt install git

Fedora

$ sudo dnf install git

openSUSE

$ sudo zypper install git

Step 2: Starting our Git repository and Committing

Git tracks changes to a particular set of files in what’s called a code repository (or “repo” for short). Let’s start one for our project. Open up your console and run these commands, in the proj3-starter directory:

Note: Check your current working directory with a pwd (Mac OS X/Linux) or cd (Windows) command before initializing the repository. You should be in the proj3-starter folder.

$ git init
Initialized empty Git repository in ~/proj3-starter/.git/
$ git config --global user.name "Your Name"
$ git config --global user.email you@example.com

Initializing the git repository is something we need to do only once per project (and you won’t have to re-enter the username and email ever again).

Git will track changes to all the files and folders in this directory, but there are some files we want it to ignore. We do this by creating a file called .gitignore in the base directory. Open up your editor and create a new file with the following contents:

*.pyc
*~
__pycache__
myvenv
db.sqlite3
/static
.DS_Store

And save it as .gitignore in the proj3-starter folder.

Some notes about the above file. Beginning a line with * matches any folder or file that ends with the following string. For example, for *.pyc, we will ignore all files that end with .pyc. myenv will match the myenv folder or file at the root.

Note: The dot at the beginning of the file name is important! If you’re having any difficulty creating it (Macs don’t like you to create files that begin with a dot via the Finder, for example), then use the “Save As” feature in your editor; it’s bulletproof.

Note: One of the files we specified in our .gitignore file is db.sqlite3. Even though we’re using a Postgres database for this project, we thought it was a good idea to add this file in, in case you decide to use sqlite for your own projects. Since the sqlite file is your local database, where all of your local data is stored, we don’t want to add this to your repository because your website on your production environment (like Heroku) is going to be using a different database. That database could be SQLite, like your development machine, but usually you will use one called MySQL or Postgres which can deal with a lot more site visitors than SQLite. Either way, by ignoring your SQLite database for the git copy, it means that all of the date that you’ve created so far (like your subreddits and posts) are going to stay and only be available locally, but you’re going to have to add them again on production. You should think of your local database as a good playground where you can test different things and not be afraid that you’re going to delete your real data when you push to production.

It’s a good idea to use a git status command before git add or whenever you find yourself unsure of what has changed. This will help prevent any surprises from happening, such as wrong files being added or committed. The git status command returns information about any untracked/modified/staged files, the branch status, and much more.

The output should be similar to the following:

$ git status
On branch master
Initial commit
Untracked files:
  (use "git add <file>..." to include in what will be committed)
  .gitignore
  manage.py
  mysite/
  reddit/
  requirements.txt
nothing added to commit but untracked files present (use "git add" to track)

Let’s commit all our changes thus far. Make sure you’re at the root proj-3-starter directory. Go to your console and run these commands:

$ git add --all .
$ git commit -m "My Reddit Production App, first commit"
 [...]
 31 files changed, 928 insertions(+)
 [...]

git add -all . tells git to stage all the changed files in our working directory. git commit stores these current changes in a new commit with a message describing the change (denoted by -m).

Step 3: Create a Heroku Account

First, we need to create a free Heroku account. Go to their Sign up page and follow the instructions to create your account. Remember your email and password as you’ll need it to set up the CLI next.

Step 4: Install Heroku Command Line Interface (CLI)

Install Heroku CLI from here. Use the instructions from earlier to see if you should use the 64-bit or 32-bit installer.

Windows

Click on the Download the installer under Windows

Mac

Click on the Download the installer under macOS.

Ubuntu / Debian

Run the following from your terminal:

$ curl https://cli-assets.heroku.com/install-ubuntu.sh | sh

Step 5: Update requirements.txt

Let’s go back to our requirements.txt file and add the following packages.

dj-database-url==0.5.0
gunicorn==19.8.1
whitenoise==3.3.1

Your requirements.txt file should now look like:

django==2.0.6
psycopg2==2.7.4
dj-database-url==0.5.0
gunicorn==19.8.1
whitenoise==3.3.1

If you’re using Django 1.11, the first line above will be django==1.11.0

These packages will help us run our Django application on Heroku. We’ll briefly go over what each package does.

  • dj-database-url — a simple Django utility that will help us generate the proper connection string for our Postgres database on Heroku.
  • gunicorn — a powerful web server that is often used in production for Django and other Python web applications.
  • whitenoise — Django doesn’t support serving static files out of the box in production. Whitenoise helps us serve static assets right from Gunicorn. Note that when you do create a production deployment where you expect a lot of users, you’d want to serve your static files through a Content Delivery Network (CDN). CDN’s help speed up delivery of these assets since they are specialized servers distributed all around the world. We won’t cover that in this walkthrough though.

Lets install all these packages. Make sure you’re inside your virtual environment.

pip install -r requirements.txt

Step 6: Restructure settings.py

A good Django practice is to separate out the settings for your development and production environment, while maintaining all the common settings in one file. In the end we will want something that looks like the following:

We’ve replaced settings.py inside mysite with a settings folder. It has three files inside of it: common.py, dev.py, and prod.py. Go ahead and create the settings folder and the three files inside of it.


Django 2.0

common.py will contain all of the common settings that don’t change between development and production. For our app that will be things such as INSTALLED_APPS, MIDDLEWARE, etc. (It is possible to have different settings for these as well between dev and prod but that’s not the case for our app). Paste the following into your common.py.

"""
Django settings for mysite project.
Generated by 'django-admin startproject' using Django 1.9.7.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '^p41e*79z0_=0^2^i7@%^4z_&ray$6zn0*o4wsly503*%m()cm'
# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'reddit'
]
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'mysite.urls'
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
WSGI_APPLICATION = 'mysite.wsgi.application'
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
LOGIN_REDIRECT_URL = '/'

Django 1.11

common.py will contain all of the common settings that don’t change between development and production. For our app that will be things such as INSTALLED_APPS, MIDDLEWARE_CLASSES, etc. (It is possible to have different settings for these as well between dev and prod but that’s not the case for our app). Paste the following into your common.py.

"""
Django settings for mysite project.
Generated by 'django-admin startproject' using Django 1.9.7.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '^p41e*79z0_=0^2^i7@%^4z_&ray$6zn0*o4wsly503*%m()cm'
# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'reddit'
]
MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'mysite.urls'
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
WSGI_APPLICATION = 'mysite.wsgi.application'
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
LOGIN_REDIRECT_URL = '/'

Alright, back to the common instructions, regardless of Django version!

On line 38, we added the whitenoise.middleware.WhiteNoiseMiddleware middleware. As we explained above, WhiteNoise allows our webapp to serve static files in production.

A middleware is a framework that hooks into Django’s request/response processing. For every request or response that goes through your Django application, your middlewares will be invoked. Each middleware component is responsible for doing some specific function. For example, Django includes a middleware component, AuthenticationMiddleware, that associates users with requests using sessions.

dev.py

dev.py will contain just the development settings. Paste the following into your dev.py file.

from .common import *
DEBUG = True
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'cl',
        'USER': 'name',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

A few important points to note:

  • We import all of the common.py settings in the first line with from .common import *
  • DEBUG is True since this file is just for development
  • Our DATABASE settings are our local database settings that we had in Part 1. Remember to replace name next to USER with your own user name that you created earlier.

prod.py

prod.py will contain just the production settings. Paste the following into your prod.py file.

from .common import *
import dj_database_url
DEBUG = False
ALLOWED_HOSTS = ['.herokuapp.com']
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'cl',
        'USER': 'name',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}
db_from_env = dj_database_url.config(conn_max_age=500)
DATABASES['default'].update(db_from_env)

A few important points to note:

  • DEBUG is False since this file is just for production.
  • Our DATABASE settings will now be our production settings. Remember to replace name next to USER with your own user name that you created earlier.
  • On lines 22, we use the dj_database_url package to generate our database connection information for Heroku. On line 23, we update our DATABASES dictionary with the updated information.
  • We’ve updated our ALLOWED_HOSTS variable to point to only ['.herokuapp.com].

Make it a package

You’ll need to create a new file inside your settings folder called __init__.py. This let’s Python know to treat settings as a package so that you can refer to the dev/prod files as mysite.settings.dev and mysite.settings.prod. You can leave this as an empty file.

Step 7: Web Server Gateway Interface (WSGI)

WSGI is the Python standard for deploying web applications. When you run the startproject command, Django automatically creates a default WSGI configuration for you. Take a look at mysite/wsgi.py — it should look like the following:

"""
WSGI config for mysite project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
application = get_wsgi_application()

Look at line 14. It settings the DJANGO_SETTINGS_MODULE to mysite.settings. This makes a lot of sense when we just had a settings.py file inside of the mysite folder, but now we’ve made settings a directory. Let’s go ahead and update this line to:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings.prod")

We’re explicitly referring to the production version of the settings file here.

Step 8: Updating manage.py

Take a look at manage.py located at the root directory (proj3-starter/manage.py). It looks like:

#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

Now we’ll also go ahead and update line 5 to use mysite.settings.prod.

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings.prod")

Development settings

You may be wondering how you’ll use manage.py in development if we’re updating DJANGO_SETTINGS_MODULE to only use the production settings file. The trick is that whenever we run a manage.py command in development, we will pass in the --settings flag. For example:

python manage.py runserver --settings=mysite.settings.dev
python manage.py migrate --settings=mysite.settings.dev

Important: This is worth repeating! Every time you use manage.py in development, always pass in the --settings flag.

Now whenever Heroku runs any of the manage.py commands, it will use the prod settings file (via the DJANGO_SETTINGS_MODULE environment variable), but whenever we run it in development, we’ll use the dev settings file via the --settings flag.

Step 9: Heroku Procfile

A Procfile is a text file in the root directory of our application that lets Heroku know what command to run to start our application. Lets create the file (called Procfile) in our root proj3-starter folder.

Add the following line and save it:

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

Let’s unpack this command. It declares a single process type, web, that will be run via the command gunicorn mysite.wsgi. The --log-file - option just tells the gunicorn command to write the logs to the console.

The name web is critical here — it lets Heroku know that this process should receive web traffic and be attached to it’s HTTP (web server) routing stack.

Step 10: Creating the runtime.txt File

We need to let Heroku know which version of Python we’ll be using. To do this, we’ll create a runtime.txt file at the root directory (same place we created the Procfile).

Simply paste in the following and save it:

python-3.6.4

Step 11: Deploying to Heroku

Hang on tight, we’re almost done!!

Let’s finish setting up Heroku and deploying to it.

Authenticate

First, we need to authenticate with Heroku. Run the following command:

$ heroku login

Log in using the email address and password you used when creating your Heroku account.

Pick a name for our Application

When you deploy your application on Heroku, it provides you a url such as <your app name>.herokuapp.com. This app name needs to be unique across all the application on Heroku. We will call ours cl-reddit-demo but you should feel free to name your’s whatever you’d like. Remember that you can only use lowercase letters, numbers, and dashes in your application name.

$ heroku create cl-reddit-demo

If you don’t want to pick a name for yourself, you can let Heroku pick one for you by running:

$ heroku create

You can always rename your application by running:

$ heroku apps:rename your-new-app-name-here

When you run the create command above, Heroku automatically adds itself as the remote repository. A remote repository is a version of our code that is hosted somewhere else on the internet or network — in our case it’s hosted on Heroku.

Pushing to Heroku

When we push our latest code to Heroku, we are updating the version of our code that lives on Heroku with our latest commit.

Remember to always commit your latest code locally before pushing to Heroku or else you’ll have an outdated version on Heroku. Let’s make sure all our changes are committed.

$ git add --all .
$ git commit -m "Your commit message here!"

You’ll know that your commit was successful if when you run git status it shows nothing to commit, working tree clean.

$ git status
On branch master
nothing to commit, working tree clean

Next we’ll push our git repository to Heroku. Run the following command:

$ git push heroku master

This might take a bit of time since Heroku has to install python, all of the packages in our requirements.txt file, etc.

Start the Web Process

Remember that we specified the web process in our Procfile. We now need to let Heroku know that it should start this process.

Run the following command:

$ heroku ps:scale web=1

This command tells Heroku to run 1 instance of our web process. These instances are known as Dynos in Heroku land.

We have a pretty simple application so we don’t need more than one Dyno, but you can imagine a popular website like Reddit having tens of thousands of Dynos serving hundreds of millions of people.

Note that if you try running more than 1 Dyno, you’ll have to start paying Heroku.

Setting up our Database and Superuser

If you were to try opening your web application right now, you’d run into a bunch of errors since we haven’t ran any migrations or created our superuser. Let’s do that now:

$ heroku run python manage.py migrate
$ heroku run python manage.py createsuperuser

When creating your superuser, remember to create a username/password that you’ll remember as you’ll need it when logging into the Django admin console and your application.

Visit your Application

You can visit your app in your browser by typing:

$ heroku open

You’ll notice that the url should be https://<your-app-name>.herokuapp.com/

Conclusion

Congratulations :). You’ve deployed your first application onto Heroku. Feel free to make any changes you like to the application. You can keep committing the changes and pushing to Heroku as many times as you like!

You can use this process to deploy any of your sample projects and share the website with your friends.


© 2016-2022. All rights reserved.