Deploy Django with PostgreSQL

Deploy a Django app on Hangar with gunicorn, a managed PostgreSQL database, migrations on every release and static files served by WhiteNoise.

Updated

This guide deploys a Django project with gunicorn as the server and a managed PostgreSQL database, both on Hangar, talking over the private network.

Prepare the project

Four changes make a Django project ready for production anywhere. If yours already has them, skip ahead.

Dependencies

Add the production packages to requirements.txt:

gunicorn
psycopg[binary]
dj-database-url
whitenoise

Settings from the environment

# settings.py
import os
import dj_database_url

SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.environ.get("DEBUG") == "1"
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",")
CSRF_TRUSTED_ORIGINS = [f"https://{host}" for host in ALLOWED_HOSTS if host]

DATABASES = {"default": dj_database_url.config(conn_max_age=600)}

dj_database_url.config() reads DATABASE_URL.

Static files

# settings.py
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ...
]
STATIC_ROOT = BASE_DIR / "staticfiles"

HTTPS behind the proxy

Hangar terminates TLS and forwards the request to your app, so tell Django to trust the forwarded protocol:

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

1. Create the database

In a new project, add a PostgreSQL service and pick a region. When it is running, open it and copy the internal connection URL.

2. Create the Django service

Add a service from your repository, in the same region as the database. Leave the builder on Automatic; it detects Python from requirements.txt (or pyproject.toml) and installs the dependencies.

3. Set the variables

In the service's Variables tab:

SECRET_KEY=<a long random string>
ALLOWED_HOSTS=app.example.com
DATABASE_URL=<the internal connection URL>

Generate the secret key locally with python -c "import secrets; print(secrets.token_urlsafe(50))".

4. Set the start command

In Settings → Runtime, set the start command so every release migrates the database, collects static files and then starts the server. The three commands are chained, so they run through a shell: set the command to sh and add two arguments, -c and this line:

python manage.py migrate && python manage.py collectstatic --noinput && gunicorn mysite.wsgi --bind 0.0.0.0:8000

Replace mysite with the name of your project package. Binding to 0.0.0.0 matters: on 127.0.0.1 the proxy cannot reach the app.

5. Deploy and add a domain

Deploy the service. Then, in Settings → Networking, add your hostname with container port 8000 and HTTPS on. Make sure the hostname is also in ALLOWED_HOSTS.

Background workers

Celery or another worker runs as a second service from the same repository, with a different start command:

celery -A mysite worker --loglevel=info

Add a Redis database as the broker and share its URL, and DATABASE_URL, as environment variables referenced by both services. See Environment variables.

Troubleshooting

  • DisallowedHost: the domain is missing from ALLOWED_HOSTS.
  • CSRF verification failed on forms: the https:// origin is missing from CSRF_TRUSTED_ORIGINS.
  • connection refused to the database: the app and the database are in different regions, or DATABASE_URL holds an external address instead of the internal one.

Frequently asked questions

How do I run Django migrations on Hangar?
Run the migration before the server in the service's start command, through a shell so the commands can be chained, setting the command to sh with the arguments -c and "python manage.py migrate && gunicorn mysite.wsgi --bind 0.0.0.0:8000". It then runs on every deploy before the app accepts traffic.
How does Django connect to PostgreSQL on Hangar?
Create a PostgreSQL database in the same project, copy its internal connection URL and set it as the DATABASE_URL variable of the Django service. The app reaches the database over the private network.
How are Django static files served on Hangar?
The simplest setup is WhiteNoise, which lets gunicorn serve the collected static files itself. Run collectstatic during the build or in the start command.

Deploy your app on Hangar

Connect a repository and it is live in minutes.

Start deploying