Deploying Django to Heroku: Connecting Heroku Postgres

Here’s everything you need to know to get your database working on Heroku.


Deploying a Django app successfully to Heroku is far from self-explanatory.

In a different post, I outlined how to get barebones Django running as a brand new Heroku app.

But Django isn’t very useful unless it has a database to talk to, and Heroku doesn’t work with Django’s default SQLite database.

Luckily, Heroku Postgres databases are free or cheap for hobby developers. In this guide, I’ll show you how to set it up.

(Are you an experienced dev just looking for some source code? Happy to oblige.)

Local vs. Remote

Let me make it clear what we’re doing here.

We have a Django app that works locally on our computer. If we run:

python manage.py runserver

Then, we’ll see a working Django app at http://localhost:8000

That local version of the Django app is using db.sqlite3 as its database.

However, when we visit the Heroku version, YourProjectName.herokuapp.com, Heroku will need to use a Postgres database instead.

What we want to do by the end of this guide is get our app running with SQLite whenever we’re working on it locally, and with Postgres whenever it’s in production.

(Alternatively, you can set up Postgres locally as well, but that’s a subject for another guide.)

DATABASE_URL

If you followed the steps in my last guide, Heroku already created an instance of Postgres for our Django app. You just didn’t know it.

In your project’s directory, using the Heroku CLI, run:

heroku config

If Heroku created a Postgres instance that’s linked to this app, you’ll see it:

=== nameless-tundra-24362 Config Vars
DATABASE_URL: postgres://[DATABASE_INFO_HERE]

I’ve removed the database info from the code snippet above because this URL is literally your database’s location and access credentials all in one.

Anyone with this URL can access your database, so be careful with it.

You’ll notice that Heroku saves it as an environment variable called DATABASE_URL . This URL can and does change, so you should never hard code it. Instead, we’ll use the variable DATABASE_URL in our application.

If your DATABASE_URL variable isn’t set, chances are Heroku doesn’t have a Postgres instance linked to your app. You may have to create it manually.

Steps to Using Postgres Remotely & SQLite Locally

There are a few key changes we need to make to our application to get everything running.

The key thing we’ll be doing here is setting DATABASE_URL to the Heroku-provided variable when we’re on Heroku. When we’re working locally, we’ll use a local file — .env — to set DATABASE_URL to point to SQLite.

That way, any time we use the DATABASE_URL variable it will point to the correct database based on the environment.

Install python-dotenv

Inside your project’s root directory (alongside manage.py), run:

pip install python-dotenv

This installs python-dotenv and also a related module called dj-database-url.

Add the new modules to your requirements.txt:

pip freeze > requirements.txt

Create .env

We’ll use a file called .env to tell Django to use SQLite when running locally.

To create .env and have it point Django to your SQLite database:

echo 'DATABASE_URL=sqlite:///db.sqlite3' > .env

Now, we don’t want .env to make it to Heroku, because .env is the part of our app that points to SQLite and Heroku doesn’t like SQLite.

So, we need git to ignore .env when pushing to Heroku. Run:

echo '.env' >> .gitignore

Update settings.py

Now that we have a .env file, we need to tell Django to use it. Hence why we downloaded the python-dotenv plugin earlier.

In your project’s settings.py make the following changes to get Django to use .env.

1. Import new modules at the top of the file:

import dj_database_url
import dotenv

2. Load environment variables from .env if it exists:

# This line should already exist in your settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# This is new:
dotenv_file = os.path.join(BASE_DIR, ".env")
if os.path.isfile(dotenv_file):
dotenv.load_dotenv(dotenv_file)

Since .env won’t exist on Heroku, dotenv.load_dotenv(dotenv_file) will never get called on Heroku and Heroku will proceed to try to find its own database — Postgres.

3. Add WhiteNoise to MIDDLEWARE:

WhiteNoise helps Django manage static files (images, scripts, etc) to load your site faster. It’s already installed by default and easy to add while we’re editing settings.py:

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]

Django security middleware should already be the first thing on the list. Never load any middleware before Django security.

Add the WhiteNoise middleware right below Django security.

Check out the WhiteNoise docs for more information.

4. Change DATABASES

Currently your settings.py has this:

DATABASES = {    
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

Change it to this:

DATABASES = {}
DATABASES['default'] = dj_database_url.config(conn_max_age=600)

The idea here is to clear the DATABASES variable and then set the 'default' key using the dj_database_url module. This module uses Heroku’s DATABASE_URL variable if it’s on Heroku, or it uses the DATABASE_URL we set in the .env file if we’re working locally.

5. Add WhiteNoise to STATICFILES_STORAGE

Django has a variable that lets it know where to store and retrieve static files. Let’s point it to WhiteNoise.

Add:

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

I recommend adding the above in the same place as your STATIC_URL and STATIC_ROOT variables.

6. sslmode issue workaround

If you ran the Django application as specified above, you might get an error when working locally because the dj_database_url module wants to log in with SSL. Heroku Postgres requires SSL, but SQLite doesn’t need or expect it.

So, we’ll use a hacky workaround to get dj_database_url to forget about SSL at the last second.

As the very last line in your settings.py, add:

# This should already be in your settings.py
django_heroku.settings(locals())
# This is new
del DATABASES['default']['OPTIONS']['sslmode']

Make Sure You Got Everything

Check your app against this barebones source code.

Test It Out!

Did we break anything about the old local build?

python manage.py runserver

It works!

Can we connect to the local SQLite database and migrate?

python manage.py migrate

Then create a superuser for the local database admin:

python manage.py createsuperuser

Check it out:

python manage.py runserver

And navigate to http://localhost:8000/admin/ and login with your newly created superuser credentials.

Did we break anything on the remote Heroku build?

Try pushing our working local app to Heroku:

git add .
git commit -am "Implement database connection"
git push heroku master

Will it build?

Counting objects: 13, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (13/13), 5.29 KiB | 0 bytes/s, done.
Total 13 (delta 6), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Python app detected
remote: -----> Installing requirements with pip
remote: Collecting python-dotenv==0.10.1 (from -r /tmp/build_647b7c85726703d0ffc33248e829a90f/requirements.txt (line 6))
remote: Downloading https://files.pythonhosted.org/packages/8c/14/501508b016e7b1ad0eb91bba581e66ad9bfc7c66fcacbb580eaf9bc38458/python_dotenv-0.10.1-py2.py3-none-any.whl
remote: Installing collected packages: python-dotenv
remote: Successfully installed python-dotenv-0.10.1
remote:
remote: -----> $ python manage.py collectstatic --noinput
remote: /app/.heroku/python/lib/python3.6/site-packages/psycopg2/__init__.py:144: UserWarning: The psycopg2 wheel package will be renamed from release 2.8; in order to keep installing from binary please use "pip install psycopg2-binary" instead. For details see: <http://initd.org/psycopg/docs/install.html#binary-install-from-pypi>
remote: """)
remote: 119 static files copied to '/tmp/build_647b7c85726703d0ffc33248e829a90f/staticfiles', 362 post-processed.
remote:
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 54.4M
remote: -----> Launching...
remote: Released v7
remote: https://nameless-tundra-24362.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/nameless-tundra-24362.git
c29842c..957955a master -> master

Success!

Check the URL just to make sure: https://nameless-tundra-24362.herokuapp.com/

Can we connect to the remote Heroku Postgres instance and migrate?

heroku run bash

This lets us into the shell for our project on Heroku. In the Heroku shell:

python manage.py migrate

Just like we did for the local version.

(In the future, when you make migrations locally, you can simply push those migrations along with the rest of your app. In Heroku’s shell you should only ever have to migrate and never makemigrations.)

Create the super user in the Postgres version of the database. In the Heroku shell, still:

python manage.py createsuperuser

Now navigate to yourprojectname.herokuapp.com/admin/ and log in to confirm everything is working!

Congratulations!

You’ve got a fully functional Django app running on Heroku with a Postgres database.

Now, you’re free to add views, urls, models, and all the other good Django stuff to your heart’s content.


Questions, issues, concerns, problems, recommendations, death threats? Let me know in the comments below.