Deploying Django on Heroku: A Step-by-Step Guide, Part 3

This article is the third in a series exploring how to effectively use pydantic in Django projects. As a quick recap, the first article demonstrated how pydantic’s use of Python type hints can simplify Django settings management. In the second tutorial, we built upon this concept by using Docker to create a web application, ensuring consistency between our development and production environments.

Deploying and updating source code can be a frustrating experience, often leading to disappointment. Having experienced my fair share of unsuccessful attempts with other platforms, I’m incredibly pleased to have found a reliable and efficient solution with Django and Heroku. In this article, I’ll share the secrets to my success through a detailed example.

Our objective is to deploy our Django application, prioritizing ease of use and security. Heroku excels in both areas, providing a smooth and secure platform for our application.

In part 2 of this series, we built a sample hello-visitor application and demonstrated how to mirror production settings in our development environment using pydantic, significantly reducing project risk.

The final step is to deploy our application using Heroku. Note: You’ll need a Basic plan account on Heroku to follow along with this tutorial.

Understanding Heroku

Heroku is a Platform-as-a-Service (PaaS) designed for deploying and managing applications. These applications, referred to as “apps,” combine our system requirements and source code. Deploying an app on Heroku involves creating a Heroku slug, which is essentially an application image that packages our configuration, add-ons, and other necessary components for deployment. Heroku slugs are comparable to Docker images.

Heroku follows a structured pipeline that consists of the following steps:

  • Build step:
    • Heroku analyzes the application source code to determine the required technologies.
    • It then builds the necessary base system image using a buildpack, in this case, heroku/python.
    • The resulting image is referred to as a slug in Heroku.
  • Release step:
    • This step allows for pre-deployment tasks and various checks on the system, settings, or data.
    • Database migrations are often performed during this stage.
  • Runtime step:
    • Heroku launches our images into lightweight containers called dynos and connects them to our add-on services, such as a database.
    • These dynos, working together, form our system infrastructure, including the necessary routers for communication between them.
    • The router, responsible for handling incoming HTTP requests, directs traffic to the appropriate web server dyno(s).
    • Scaling is made easy with Heroku’s dynamic dyno provisioning based on load.

With a basic understanding of Heroku’s workings and terminology, let’s explore the straightforward process of deploying our sample application.

Installing the Heroku CLI

To interact with Heroku, we need its command-line interface installed installed locally. We’ll use the standard snap installation method on an Ubuntu development machine. For other platforms, the Heroku documentation provides detailed installation instructions.

1
2
3
4
sudo snap install --classic heroku
# check that it works
heroku --version

Next, we need to configure our local Heroku tools with our credentials using the authentication step:

1
heroku login

This command will securely store our email address and an API token in the ~/.netrc file for future use.

Creating a Heroku App

With Heroku installed, the next step is to creating our app. This app serves as a reference to our source code repository and lists the required add-ons.

It’s crucial to note that Heroku requires each application to have a unique name across all users. Therefore, we cannot use a common example name for this tutorial. Choose a name that resonates with you and use it consistently throughout the instructions. While our screenshots will display hello-visitor as the app name, remember to replace it with your unique name.

We’ll utilize the basic Heroku scaffolding command to create our app:

1
heroku apps:create <UNIQUE-APP-NAME-HERE>

Adding the PostgreSQL Add-on

Our Django project relies on a relational database, as outlined in part 2 of this series. Configuring the necessary add-ons is done through the Heroku browser interface. Follow these steps:

  1. Go to the Resources tab in your Heroku dashboard to manage add-ons.
  2. Select Postgres and specifically choose heroku-postgresql for installation.
    1. Opt for the Mini add-on plan.
  3. Link this add-on to your uniquely named app.
  4. Submit the order form.
A Heroku administration page called Online Order Form shows that Postgres is being selected as an add-on to the hello-visitor app. This database is being added under the Heroku Mini plan as selected from a drop-down menu. A purple Submit Order Form button is at the bottom.
Online Order Form

Once PostgreSQL is provisioned and linked to our app, the database connection string will be accessible in our app’s configuration variables. To view it, navigate to Settings and click on Reveal Config Vars. You’ll find a variable named DATABASE_URL:

1
DATABASE_URL=postgres://{user}:{password}@{hostname}:{port}/{database-name}

As highlighted in parts 1 and 2, our application leverages the power of pydantic and environment variables. Heroku automatically exposes its Config Vars to the application environment, eliminating the need for code modifications when deploying to production. We won’t delve into the specifics of each setting, leaving that as an exercise for you.

Configuring the Application Pipeline

Earlier, we discussed the key steps involved in Heroku’s pipeline for building, configuring, and deploying an app. Each step is associated with files that contain the corresponding settings and commands.

Build Step Configuration

We need to specify the technology stack for Heroku to use. Since our app relies on Python and a set of dependencies outlined in its requirements.txt file, and Heroku defaults to a recent Python version (currently 3.10.4), we can skip explicit build configuration for now.

Release Step Configuration

Heroku’s release step, which occurs before deployment, is defined by a command specified in our app’s hello-visitor/Procfile. Following best practices, we’ll create a separate shell script to list the commands or scripts to be executed. Heroku will always run the contents of hello-visitor/Procfile.

Since we don’t have a script yet, let’s create our release script, hello-visitor/heroku-release.sh, instructing Heroku to enhance deployment security and automatically run database migrations. Add the following code:

1
2
3
4
# file: hello-visitor/heroku-release.sh
cd src
python manage.py check --deploy --fail-level WARNING
python manage.py migrate

As with any shell script, we need to make it executable. Use the following command on Unix distributions:

1
chmod +x heroku-release.sh

Now that our release script is ready, add it to your app’s hello-visitor/Procfile to ensure it runs during the release phase. Create the the Procfile and add the following:

1
2
# file: hello-visitor/Procfile
release: ./heroku-release.sh

With the release step configured, let’s move on to defining the deployment step.

Deployment Step Configuration

Our goal is to start a web server with two worker nodes.

Again, following best practices, we’ll create a separate deployment script named heroku-web.sh in our project root directory. Add the following content:

1
2
3
# file: hello-visitor/heroku-web.sh
cd src
gunicorn hello_visitor.wsgi --workers 2 --log-file -

Make the script executable:

1
chmod +x heroku-web.sh

Update your app’s Procfile to include the deployment script, ensuring it runs at the appropriate stage:

1
2
3
# file: hello-visitor/Procfile
release: ./heroku-release.sh
web: ./heroku-web.sh

With our Heroku app pipeline defined, the next step is to set up environment variables for our source code. This step follows logically after defining the Heroku app. Our deployment will fail without these variables, as our source code relies on them.

Setting Up Environment Variables

Django requires a secret key, SECRET_KEY, for proper operation. This key, along with other variables, will be stored in our app’s environment variable collection. Before configuring them, let’s generate our secret key, ensuring it doesn’t contain special characters by using base64 encoding (not UTF-8). Base64 avoids non-alphanumeric characters like + or @, which can cause issues when provisioning secrets as environment variables. Generate the SECRET_KEY using the following Unix command:

1
openssl rand -base64 70

Now we can configure our environment variables as Heroku Config Vars.

Previously, we examined the database connection string in the Config Vars admin panel. Navigate back to this panel to add new variables and their values:

Key

Value

ALLOWED_HOSTS

["hello-visitor.herokuapp.com"]

SECRET_KEY

(Use the generated key value)

DEBUG

False

DEBUG_TEMPLATES

False

At this stage, our Heroku app has a fully configured deployment pipeline and environment variables. The last step is to connect Heroku to our source code repository.

Linking to GitHub Repository

Instruct Heroku to associate our app with our GitHub repository by following these steps:

  1. Go to the Deploy tab in your Heroku dashboard.
  2. Authenticate your Heroku account with GitHub (a one-time process).
  3. Navigate to the Admin panel for your Heroku app.
  4. Choose GitHub from the Deployment method dropdown. This will display a list of available repositories from your GitHub account.
  5. Select the desired GitHub repository.
  6. Heroku will establish a connection with your chosen repository.

Your dashboard should now resemble the following:

Heroku’s administration deployment tab is shown. On the top, GitHub is shown as connected. At the bottom, the GitHub repository "ArjaanBuijk/hello-visitor" is shown as connected by the user "ArjaanBuijk". A red Disconnect button is to the right of this information.
Heroku’s Deploy Tab

To deploy our app manually, navigate to the manual deploy section, select the main branch of your repository, and click the Deploy Branch button.

A Heroku administration deployment panel shows the application’s repository branch for Django app deployment with “main” selected under “Enter the name of the branch to deploy.” There is a black button labeled Deploy Branch at the bottom right.
Heroku Deployment

If everything goes smoothly, our deployment will succeed, utilizing our defined build and release scripts, and the website will be deployed.

Testing the Deployed Application

To test the deployed application, click the Open App button located at the top of the Heroku App dashboard.

The webpage will show a visitor count that increases with each refresh.

Achieving Smoother Django App Deployments

This deployment process is incredibly straightforward. The configuration is simple, and the core Heroku buildpack, coupled with the platform itself, handles most of the heavy lifting. Moreover, the core Heroku Python buildpack is open source, making this approach applicable to other application platforms.

Combining this ease of deployment with the power of a mirrored environment and pydantic settings management results in a robust, environment-independent deployment that works seamlessly both locally and on the web.

By adopting this Django settings management strategy, you’ll have a single settings.py file that configures itself using environment variables.


The Toptal Engineering Blog would like to thank Stephen Davidson for his contributions in reviewing and beta testing the code samples presented in this article.

Licensed under CC BY-NC-SA 4.0