Context

I am currently working on building a website for my wife to showcase her art. Apart from a simple static blog (this one) and some experiments with static pages and API development, the website I am about to build will be my first web development project. So I am learning a lot of things as I go. The plan is to use Django as the framework and host the site on Heroku to begin with, to keep things simple.

In this post I give a quick overview of the commands required to deploy a simple barebones Django project to Heroku. This is very much a proof-of-concept still, but it already makes me happy just to know the process is working as expected.

Pre-Requisites

  • An existing Heroku account
    • Can be created free of charge
  • Having the Heroku CLI installed

Commands

Create a project folder, virtual environment and bootstrap the Django project:

# Create project folder
mkdir demo_project
cd demo_project

# Create virtual environment
python3 -m venv venv --copies
source venv/bin/activate
pip install django==3.2.5 django-heroku # Note: django-heroku is deprecated but still works..

# Set up Django Project
django-admin startproject demo_project . # Note: only lowercase letters and underscores allowed

Prepare the local repository and create a Heroku project:

# Initialise local git repo
git init

# Create `.gitignore`
curl https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,django > .gitignore
# Note: We used http://gitignore.io/ to dynamically generate a suitable .gitignore file

git status
git add .
git commit -m "Initial commit"

# Connect with Heroku account
heroku login

# Create Heroku Project
heroku create
# Note: Heroku will create a new project and assign a random name
# The Heroku CLI will also automatically configure the remote repo for us
# We can verify this with `git remote -v`
# To set a custom name use `heroku create django-heroku-demo-122021`
# Note: The name must be globally unique, and have a maximum of 30 characters

# Verify that the git remote is correctly configured
git remote -v

# Inspect the details of our freshly created Heroku application
heroku info

Adjust the Django configuration to make it compatible for deployment on Heroku:

###############################################
# Prepare Django app configuration for Heroku #
###############################################

echo "import django_heroku" >> demo_project/settings.py
echo "django_heroku.settings(locals())" >> demo_project/settings.py
echo "web: python manage.py runserver 0.0.0.0:\$PORT" > Procfile
pip freeze > requirements.txt
# Note: Later I would use `pip-compile` to track all sub-dependencies and their lineage explicitly
# Sample usage: `pip install pip-tools; echo "django" > requirements.in`
# And then: `pip-compile requirements.in; pip install -r requirements.txt`

# Configure ALLOWED_HOSTS
# Note: If we don't do this, the deploy will succeed but the deployed Django app will throw an error

# Step 1: Obtain the url of the application
app_url=`heroku apps:info -s | grep web_url`
echo $app_url

# Step 2: Extract only the relevant part of the url via sed and a regex capture group
app_url_parsed=`echo $app_url | sed -E 's|web_url=https:\/\/(.*)\/|\1|'`
echo $app_url_parsed
# Notes:
# Literal `/`characters need to be escaped with `\`
# I used `|` instead of `/` as the separator for clarity
# -e flag for "extended regular expression syntax" to allow for capture groups
# `\1` to reference the first capture group
# The general pattern is `sed 's/pattern/replacement/'

# Step 3: Add the app url to ALLOWED_HOSTS

# Replace ALLOWED_HOSTS config in settings.py and store result in temp_file
cat demo_project/settings.py | sed -e "s|ALLOWED_HOSTS = \[\]|ALLOWED_HOSTS = \[\'$app_url_parsed\'\]|" > temp_file
# Overwrite the settings with the changes
mv temp_file demo_project/settings.py

# Handle SECRET_KEY safely
# Note: Secrets should always be passed via environment variables
# Use `.env` file locally and `heroku config:set KEY=VALUE` for heroku

# Step 1: Generate a random secret and store it in .env
echo "SECRET_KEY=$(openssl rand -base64 32)" > .env

# Step 2: Obtain the secret as a shell variable
SECRET_KEY=`cat .env | sed -E 's|SECRET_KEY=(.*)|\1|'`

# Step 3: Set the secret as an environment variable with Heroku
heroku config:set SECRET_KEY="$SECRET_KEY"

# Step 4: Replace the initial secret key in the settings with an import from the environment variable
sed -i -e "s/.*SECRET_KEY.*/SECRET_KEY = os.environ['SECRET_KEY']/" demo_project/settings.py
# -i to make changes _in-place_
# The `os.environ['key']` syntax ensures an error is raised, if the secret is missing

# Step 5: Add `import os` to the top of the settings
sed -i '.bak' '1s/^/import os\'$'\n/g' demo_project/settings.py
# This generates a backup file `settings.py.bak` and modifies the orig. file in-place
# NOTE: placing the import at the _very top_ of the file is not good
# I rather would place it 'in the line above the secret key'
# But I couldn't quite get that to work on macOS with BSD sed
# For this demo having the import at the top of the file will suffice
rm demo_project/settings.py.bak # Remove backup

# Handle DEBUG variable
# We want to explicitly turn on or off the DEBUG setting
# Turn off by default

# Step 1: Make adjustment to load DEBUG setting from env, with False as Default
sed -i -e "s/.*DEBUG.*/DEBUG = os.environ.get('DEBUG', False)/" demo_project/settings.py
# Note: The `os.environ.get('key',<default>)` syntax sets a default if the key is missing

# Step 2: Add DEBUG setting to local .env file
echo "DEBUG=True" >> .env

# Step 3: Set the environment var for use with Heroku
heroku config:set DEBUG=False # Set explicitly to false
# heroku config:set `cat .env | grep DEBUG` # Same setting as in local .env

# Quick santiy-check
git diff
cat .env
heroku config

Commit changes and deploy to Heroku:

# Commit current state
git status
git add .
git commit -m "Prepare django project for demo deployment"

# Deploy project to Heroku
git push heroku master
# Note: Can use `git push heroku feature/demo:master` to deploy from another branch

# Open website and verify it is working
heroku open

And that's it. The basic Django app skeleton should now be accessible online. :-)

Cleaning up:

# Delete the demo application
heroku apps:destroy
# Confirm by typing in the name of the app

Next Steps

  • Create an app with python manage.py startapp my_app
  • Configure the URL routes to include routes for the new app
  • Create some views
  • Create data model(s)
  • Make migrations via python manage.py makemigrations
  • Run migrations via python manage.py migrate
  • Test locally via python manage.py runserver
  • Set up a proper database
  • Handle static files
  • Continue building out the site..

Bonus – Useful Heroku commands

heroku login
heroku create

heroku info
heroku status
heroku config
heroku config:set KEY=VALUE
heroku config:unset KEY

heroku apps
heroku apps:info -s # -s to get output suitable for shell
app_url=`heroku apps:info -s | grep web_url`

Reference / Further Reading


Published

Category

Web Development

Tags

Contact