Getting from runserver to a reliable production service involves a well-known checklist.
A production Django deployment usually looks like this:
- App server:
gunicorn mysite.wsgi(WSGI) oruvicorn mysite.asgi:application/gunicorn -k uvicorn_worker.UvicornWorker mysite.asgi(ASGI, for async views and WebSockets via Channels) - Reverse proxy or load balancer in front for TLS, buffering and serving static files
- Database such as PostgreSQL, with persistent connections (
CONN_MAX_AGE, plusCONN_HEALTH_CHECKS=True) or a pooler; Django 5.1+ also supports psycopg's native connection pool on PostgreSQL - Static files from
collectstatic, served by WhiteNoise, the proxy or a CDN; media uploads on object storage viadjango-storages
Release steps, in order: build the image or artifact, python manage.py collectstatic --noinput, python manage.py migrate as a separate one-off step, then start new app processes.
Configuration checklist (python manage.py check --deploy covers much of it):
DEBUG = False, realSECRET_KEY,ALLOWED_HOSTS,CSRF_TRUSTED_ORIGINSfor HTTPS origins if needed- HTTPS settings and
SECURE_PROXY_SSL_HEADERbehind a proxy LOGGINGto stdout in containers, plus error tracking (such as Sentry);ADMINSemails are an option on small sites- caches on Redis, sessions in the database or cache
- a health check URL that touches the database
Write migrations to be backward compatible with the running code, since old and new versions overlap during a rolling deploy.
Example
# Dockerfile (excerpt)
FROM python:3.13-slim
ENV PYTHONUNBUFFERED=1 DJANGO_SETTINGS_MODULE=mysite.settings
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN DJANGO_SECRET_KEY=build-only python manage.py collectstatic --noinput
RUN useradd --create-home app
USER app
CMD ["gunicorn", "mysite.wsgi", "--bind", "0.0.0.0:8000", "--workers", "3", "--access-logfile", "-"]
# release step (run once per deploy, before switching traffic):
# python manage.py migrate --noinput
# CI:
# python manage.py check --deploy --fail-level WARNINGRemember
- Gunicorn (WSGI) or Uvicorn (ASGI) behind a reverse proxy
- collectstatic at build time, migrate as a separate release step
- check --deploy, persistent DB connections, stdout logging
- Keep migrations compatible with the previous code version
Watch out for
- Running migrations automatically in every app process at startup, racing each other.