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 fromALLOWED_HOSTS.- CSRF verification failed on forms: the
https://origin is missing fromCSRF_TRUSTED_ORIGINS. connection refusedto the database: the app and the database are in different regions, orDATABASE_URLholds an external address instead of the internal one.