All articles
2 min read

Deploying a Django App on Heroku with PostgreSQL

A step-by-step guide to deploying a Django application on Heroku with a PostgreSQL database, including production settings, static files, and environment variable configuration.

Heroku is one of the fastest ways to get a Django application into production — no server management, built-in PostgreSQL, and deployments via git push. This guide walks through everything from local setup to a live URL, including the production settings you actually need.

Prerequisites

  • Django project already working locally
  • Heroku CLI installed
  • Git initialized in your project

Step 1: Install Required Packages

pip install gunicorn psycopg2-binary dj-database-url whitenoise
  • gunicorn — production WSGI server
  • psycopg2-binary — PostgreSQL adapter
  • dj-database-url — parses DATABASE_URL into Django's DATABASES dict
  • whitenoise — serves static files without a separate CDN

Step 2: Update settings.py for Production

Add these settings to your settings.py. Use environment variables for sensitive values:

import os
import dj_database_url

# Security
SECRET_KEY = os.environ.get("SECRET_KEY", "your-local-dev-key")
DEBUG = os.environ.get("DEBUG", "False") == "True"
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost").split(",")

# Database — reads DATABASE_URL env var set by Heroku automatically
DATABASES = {
    "default": dj_database_url.config(
        default="sqlite:///db.sqlite3",
        conn_max_age=600,
        ssl_require=not DEBUG,
    )
}

# Static files with WhiteNoise
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",  # after SecurityMiddleware
    # ... rest of middleware
]

STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

Step 3: Create a Procfile

In your project root:

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

Replace myproject with the name of your Django project (the directory containing wsgi.py).

Step 4: Specify the Python Runtime

Create runtime.txt in the project root:

python-3.11.9

Check Heroku's supported runtimes for the latest versions.

Step 5: Generate requirements.txt

pip freeze > requirements.txt

Step 6: Create the Heroku App

heroku login
heroku create your-app-name

Step 7: Add PostgreSQL

heroku addons:create heroku-postgresql:essential-0 --app your-app-name

Heroku automatically sets the DATABASE_URL environment variable.

Step 8: Set Environment Variables

heroku config:set SECRET_KEY="your-secret-key-here"
heroku config:set DEBUG="False"
heroku config:set ALLOWED_HOSTS="your-app-name.herokuapp.com"

Generate a secure SECRET_KEY:

python -c "import secrets; print(secrets.token_urlsafe(50))"

Step 9: Deploy

git add .
git commit -m "Configure for Heroku deployment"
heroku git:remote -a your-app-name
git push heroku main

If your default branch is master, use git push heroku master instead.

Step 10: Run Migrations and Create Superuser

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

Step 11: Collect Static Files

heroku run python manage.py collectstatic --noinput

Step 12: Open Your App

heroku open

Monitoring

heroku logs --tail          # stream live logs
heroku ps                   # check running dynos
heroku config               # view all environment variables

Common Issues

DisallowedHost error — Your app domain isn't in ALLOWED_HOSTS. Update the env var:

heroku config:set ALLOWED_HOSTS="your-app-name.herokuapp.com"

Static files not loading — Ensure WhiteNoiseMiddleware is listed directly after SecurityMiddleware in MIDDLEWARE and that you've run collectstatic.

Database connection errorspsycopg2-binary may conflict with compiled versions. Try psycopg2 instead if you see binary/library errors.

Conclusion

With Heroku, a working Django app can be live in under 30 minutes. The most common pitfalls are missing production settings (DEBUG=False, ALLOWED_HOSTS, SECRET_KEY from environment) and static files not being configured with WhiteNoise. Get those right and the rest is straightforward.