This is the third installment in a series on leveraging pydantic for Django-based projects. Part 1 focused on pydantic’s use of Python type hints to streamline Django settings management; part 2 built an app on this concept with Docker and conda to show how to align development and production environments.
Deploying source code—and redeploying after updates—can be a frustrating process that leaves you brokenhearted. After so many bad relationships with other deployment platforms, I feel lucky to have found lasting happiness with Django and Heroku. I want to share the secrets of my success through a carefully curated example.
We want to deploy our Django application and ensure it is easy and secure by default. Heroku provides a no-stress relationship with our application platform by combining efficiency and security.
We have already built a sample
hello-visitor application in part 2 of this Django and pydantic tutorial series and discussed how our development environment should mirror our production settings using pydantic. This mirroring removed considerable risk from our project.
The remaining task is to make our application available on the web using Heroku. Note: In order to complete this tutorial, you must sign up for an Eco plan account at Heroku.
Heroku is a Platform-as-a-Service, and it serves applications. Those applications, called apps, couple our system requirements and source code. To put our app on Heroku, we must create a Heroku slug—an application image that combines our configuration, add-ons, and more to create a deployable release. Heroku slugs are comparable to Docker images.
Heroku goes through a well-orchestrated pipeline with these steps:
- Build step:
- Heroku inspects our application source and determines what technologies are required.
- Heroku builds the required base system image for our application using a buildpack, which in this case is heroku/python.
- The resulting image is called a slug in Heroku.
- Release step:
- Heroku allows us to do pre-deployment work or perform various checks on our system, settings, or data.
- Database migrations are common during this step.
- Runtime step:
- Heroku spins up our images into lightweight containers called dynos and connects them to our add-on services, e.g., a database.
- One or more dynos constitute our system infrastructure, including required routers to enable intra-dyno communication.
- Incoming HTTP requests also fall within the router’s responsibilities, where traffic links to the appropriate web server dyno(s).
- Scaling out is simple because Heroku allows for dynamic provisioning of dynos based on load.
Now that we understand how Heroku works and its basic terminology, we will show how straightforward it is to deploy our sample application.
Install Heroku CLI
We need Heroku’s command-line interface installed locally. Using the standard snap installation makes this simple—we will demonstrate this on an Ubuntu development machine. The Heroku documentation provides additional steps to install its toolset on other platforms.
sudo snap install --classic heroku # check that it works heroku --version
We must configure our local Heroku tools with our credentials via the authentication step:
This will save our email address and an API token into the
~/.netrc file for future use.
Create Heroku App
With Heroku installed, creating our app is the initial step toward deploying our source code. This app not only points to our source code repository, but also enumerates which add-ons we need.
A critical note about Heroku deployment is that every application must have a unique name for every person using Heroku. Therefore, we cannot use a single example name while going through these steps. Please pick a name that makes you happy and plug that into the instruction block throughout this tutorial. Our screenshots will list the app name as
hello-visitor, but as you follow along, your uniquely chosen name will appear in those locations instead.
We use the basic Heroku scaffolding command to create our app:
heroku apps:create <UNIQUE-APP-NAME-HERE>
The PostgreSQL Add-on
Our app requires a relational database for our Django project, as mentioned in part 2 of our series. We configure required add-ons through the Heroku browser interface with the following steps:
- Navigate to the Resources tab in the Heroku dashboard to configure add-ons.
- Ask Heroku to install Postgres, specifically heroku-postgresql.
- Choose the Mini add-on plan.
- Associate this add-on with our uniquely named app.
- Click Submit Order Form.
Once PostgreSQL has been provisioned and associated with our app, we can see our database connection string in our app’s configuration variables. To demonstrate this, we navigate to Settings and click on Reveal Config Vars, where we see a variable
As explained in parts 1 and 2 in our series, the power inherent in our application comes from the elegant use of pydantic and environment variables. Heroku makes its Config Vars available in the application environment automatically, which means our code doesn’t require any changes to host in our production environment. We won’t explore each setting in detail, but will leave this as an exercise for you.
Configuring Our Application Pipeline
When we introduced Heroku above, we detailed the key steps in its pipeline that are needed to create, configure, and deploy an app. Each of these steps has associated files containing the appropriate settings and commands.
Configure the Build Step
We need to tell Heroku which technology stack to use. Our app uses Python and a set of required dependencies, as listed in its
requirements.txt file. If we want our app to use a recent Python version (currently defaulted to version 3.10.4) Heroku doesn’t require us to explicitly identify which Python version to use for the build step. Therefore, we will skip explicit build configuration for now.
Configure the Release Step
Heroku’s release step, done pre-deployment, has an associated command specified in our app’s
hello-visitor/Procfile. We follow best practices by creating a separate shell command listing the commands or dependent scripts we want to run. Heroku will always read the
hello-visitor/Procfile file and execute its contents.
We don’t have a script to refer to in that file yet, so let’s create our release shell script,
hello-visitor/heroku-release.sh, and ask Heroku to secure our deployment and perform database migrations automatically with the following text:
# file: hello-visitor/heroku-release.sh cd src python manage.py check --deploy --fail-level WARNING python manage.py migrate
As with any user-created shell script, we must ensure it is executable. The following command makes our script executable on Unix distributions:
chmod +x heroku-release.sh
Now that we have written our release script, we add it to our app’s
hello-visitor/Procfile file so that it will run during release. We create the
Procfile and add the following content:
# file: hello-visitor/Procfile release: ./heroku-release.sh
The fully configured release step leaves only the deployment step definition before we can do a test deployment.
Configure the Deployment Step
We will configure our app to start a web server with two worker nodes.
As we did in our release section, we will follow best practices and create a separate shell script containing the deployment operations. We will call this deployment script
heroku-web.sh and create it in our project root directory with the following contents:
# file: hello-visitor/heroku-web.sh cd src gunicorn hello_visitor.wsgi --workers 2 --log-file -
We ensure our script is executable by changing its system flags with the following command:
chmod +x heroku-web.sh
Now that we have created our executable deployment script, we update our app’s
Procfile so that the deployment step runs at the appropriate phase:
# file: hello-visitor/Procfile release: ./heroku-release.sh web: ./heroku-web.sh
Our Heroku app pipeline is now defined. The next step is to prepare the environment variables used by our source code because this follows the Heroku app definition screen in order. Without these environment variables, our deployment will fail because our source code relies on them.
Django requires a secret key,
SECRET_KEY, to operate correctly. This key will be stored, along with other variables, in our app’s associated environment variable collection. Before we fully configure our environment variables, let’s generate our secret key. We must ensure there are no special characters in this key by encoding it with base64 (and not UTF-8). base64 does not contain non-alphanumeric characters (e.g., +, @) that may cause unexpected results when secrets are provisioned as environment variables. Generate the
SECRET_KEY with the following Unix command:
openssl rand -base64 70
With this key in hand, we may now configure our environment variables as Heroku’s Config Vars.
Earlier, we looked at the database connection string in the Config Vars administration panel. We must now navigate to this administration panel to add variables and specific values:
(Use the generated key value)
At this point, our Heroku app has all the steps in the deployment pipeline configured and our environment variables set. The final configuration step is pointing Heroku at our source code repository.
Now we ask Heroku to associate our app with our GitHub repository with the following instructions:
- Navigate to the Deploy tab in the Heroku dashboard.
- Authenticate our Heroku account with GitHub (only done once).
- Navigate to the Admin panel for our Heroku app.
- In the Deployment method dropdown, select GitHub. Heroku will then show a list of available projects in our GitHub account.
- We select our GitHub repository.
- Heroku connects to the GitHub repository.
After that, our dashboard should look like the following:
We may now manually deploy our app by navigating to the manual deploy section, selecting our repository’s
main branch, and clicking the Deploy Branch button.
If all goes well, our deployment will correctly complete using our defined build and release scripts and deploy the website.
A Test Run
We can try out the deployed application by clicking the Open App button at the top of the Heroku App dashboard.
The webpage will show the number of site visitors, which increases each time you refresh the page.
Smoother Django App Deployments
In my opinion, this deployment couldn’t be any easier. The configuration steps are not cumbersome, and the core Heroku buildpack, lovingly cradled by the Heroku platform, does almost all the heavy lifting. Better yet, the core Heroku Python buildpack is open source, and many other application platforms use it. So the approach you have learned in this tutorial is a highly transferable skill.
When we couple deployment ease with the magic of the mirrored environment and pydantic settings management, we have a stable, environment-independent deployment that works locally and on the web.
By following this Django settings management approach, you end up with a single
settings.py that configures itself using environment variables.
The Toptal Engineering Blog extends its gratitude to Stephen Davidson for reviewing and beta testing the code samples presented in this article.