Framework guides

Deploy Django on Zenifra

This guide takes a Django project from a GitHub repository to production, with static files, a managed database, and secure settings.

Prerequisites

  • A Django project in a GitHub repository, with manage.py at the root
  • requirements.txt with django, gunicorn, whitenoise, and the database driver
  • GitHub account connected to Zenifra
requirements.txt
Django==5.1.1
gunicorn==23.0.0
whitenoise==6.7.0
psycopg[binary]==3.2.2
dj-database-url==2.2.0

The examples assume the settings package is called config (config/settings.py, config/wsgi.py). Replace it with your project's name.

Adjust settings.py

config/settings.py
import os
import dj_database_url

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true"

ALLOWED_HOSTS = [".clients.zenifra.com"] + [
    h for h in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",") if h
]
CSRF_TRUSTED_ORIGINS = ["https://*.clients.zenifra.com"] + [
    f"https://{h}" for h in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",") if h
]

# Zenifra terminates HTTPS and reports the original protocol in this header
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

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

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ... other middleware
]

STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
    "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
    "staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}

WhiteNoiseMiddleware must come right after SecurityMiddleware.

Environment variables

VariableExamplePurpose
DJANGO_SECRET_KEYlong random valueSigns sessions and tokens
DATABASE_URLpostgres://user:password@host:5432/dbDatabase connection
DJANGO_ALLOWED_HOSTSapp.mycompany.comCustom domains, comma-separated
DJANGO_DEBUGfalseNever true in production

Generate a key with python -c "import secrets; print(secrets.token_urlsafe(50))". Variables also exist during the build, which lets collectstatic run with the full settings.py.

Console configuration

FieldValue
RuntimePython 3.12
Port8000
pre-buildempty
buildpython manage.py collectstatic --noinput
startpython manage.py migrate --noinput && gunicorn config.wsgi --bind 0.0.0.0:8000 --workers 2 --access-logfile -

Why migrate in start?

In start, the migration runs with database access in the app's final environment, right before the server comes up. Django migrations use transactions on PostgreSQL, so a failure does not leave the schema half-applied.

User uploads (MEDIA)

Files uploaded by users cannot live on the instance's ephemeral disk. Choose one path:

  • Persistent storage with the /app/media directory and MEDIA_ROOT = "/app/media". To serve those files, use your own view or an object storage service.
  • An S3-compatible object storage service with django-storages.

Background tasks with Celery

Deploy the Celery worker as another project, from the same repository, with start set to celery -A config worker --loglevel=info. Use the managed cache or managed queues as the broker.

Create the superuser

Set DJANGO_SUPERUSER_USERNAME, DJANGO_SUPERUSER_EMAIL, and DJANGO_SUPERUSER_PASSWORD, and prepend this to start once:

python manage.py createsuperuser --noinput || true

After the user exists, remove the command and the password variable.

Common problems

SymptomFix
DisallowedHostAdd the domain to ALLOWED_HOSTS or DJANGO_ALLOWED_HOSTS
CSRF verification failedAdd https://your-domain to CSRF_TRUSTED_ORIGINS
CSS and JS return 404collectstatic did not run or WhiteNoise is missing from MIDDLEWARE
Infinite redirect with SECURE_SSL_REDIRECTSet SECURE_PROXY_SSL_HEADER as in the example
KeyError: 'DJANGO_SECRET_KEY' during the buildAdd the variable to the project before deploying
too many connectionsLower --workers or conn_max_age; see Connection problems

Next steps

Last updated on

On this page