Deploying a Django App
Django's built-in runserver is explicitly documented as unsuitable for production — it's single-threaded, has no process management, and serves static files inefficiently. A production deployment instead runs your application through a WSGI server like Gunicorn (or an ASGI server like Uvicorn/Daphne if you use async views or Channels), which Gunicorn workers handle concurrently, fronted by a reverse proxy like nginx that terminates TLS, serves static/media files directly, and forwards dynamic requests to Gunicorn over a Unix socket or local port.
Cricket analogy: Using runserver in production is like fielding a club-level net bowler in a World Cup final — fine for practice, but you need international-standard 'workers' (gunicorn processes) to handle real match pressure.
WSGI Servers and Static Files
Gunicorn is invoked with something like gunicorn myproject.wsgi:application --workers 3 --bind 0.0.0.0:8000, and the worker count is typically set to roughly (2 x CPU cores) + 1. Django itself does not serve static files (CSS, JS, images) efficiently or at all with DEBUG=False, so production deployments run python manage.py collectstatic to gather every app's static assets into STATIC_ROOT, then either let nginx serve that directory directly or use WhiteNoise middleware to serve compressed, cache-busted static files straight from the Django process itself, which is simpler when you don't want to manage a separate nginx static-file config.
Cricket analogy: collectstatic is like a groundsman gathering every piece of equipment scattered across different team dressing rooms into one central kit room before the tournament starts, so it's all in one place for match day.
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
CMD ["gunicorn", "myproject.wsgi:application", \
"--workers", "3", "--bind", "0.0.0.0:8000"]
Database and Migrations in Production
Production deployments almost always use a managed database service (Amazon RDS, DigitalOcean Managed Postgres, or a self-hosted Postgres container with backups) rather than SQLite, which is fine for development but doesn't handle concurrent writes well. During deployment, python manage.py migrate must run against the production database before the new application code starts serving traffic — most teams run this as an explicit release step (a Kubernetes init container, a pre-deploy hook, or a CI/CD pipeline stage) rather than automatically on every container boot, to avoid multiple app instances racing to apply the same migration simultaneously.
Cricket analogy: Running migrations before the new app version goes live is like the ground staff finishing pitch preparation before the umpires call 'play' — you don't want batsmen (requests) arriving at a pitch (schema) that's still being relaid.
A common release pipeline order is: build the new image, run migrate as a one-off job against production, then perform a rolling deploy of app instances pointing at the now-migrated schema. This avoids old and new code running simultaneously against mismatched schema versions for long.
Containerizing with Docker
A typical production Docker setup uses a Dockerfile that installs dependencies, runs collectstatic, and starts Gunicorn as the container's entrypoint, orchestrated alongside a Postgres container and an nginx container via docker-compose.yml (or Kubernetes manifests for larger deployments). nginx is configured as a reverse proxy that terminates TLS certificates (often via Let's Encrypt/Certbot), serves the STATIC_ROOT volume directly for speed, and proxies everything else to the Gunicorn container's internal port, which keeps Django itself completely unreachable from the public internet.
Cricket analogy: nginx acting as the public-facing reverse proxy is like a team's media manager who handles all press and public interaction, while the players (Django/gunicorn) stay in the dressing room, never directly exposed to reporters.
Never expose Django's development server or the Gunicorn port directly to the public internet without a reverse proxy, and always confirm DEBUG=False, a proper ALLOWED_HOSTS list, and HTTPS-only cookie settings are active before pointing DNS at a production deployment.
- runserver is unsuitable for production; use Gunicorn (WSGI) or Uvicorn/Daphne (ASGI) fronted by nginx as a reverse proxy.
- Gunicorn worker count is typically (2 x CPU cores) + 1, tuned based on load testing.
- python manage.py collectstatic gathers static assets into STATIC_ROOT for nginx or WhiteNoise to serve.
- Production databases should be managed Postgres/MySQL services, not SQLite, which struggles with concurrent writes.
- Run migrate as an explicit release step before new app instances start serving traffic, avoiding concurrent migration races.
- nginx terminates TLS, serves static files directly, and reverse-proxies dynamic requests to Gunicorn, keeping Django off the public internet directly.
- Always verify DEBUG=False, ALLOWED_HOSTS, and secure cookie settings before going live.
Practice what you learned
1. Why is Django's runserver unsuitable for production traffic?
2. What is a typical formula for setting Gunicorn worker count?
3. What does python manage.py collectstatic do?
4. Why should migrations typically run as a distinct release step rather than automatically on every container boot?
5. What role does nginx typically play in a production Django deployment?
Was this page helpful?
You May Also Like
Django Settings and Environments
Understand how Django's settings module works and how to structure configuration safely across local, staging, and production environments.
Testing Django Applications
Learn how Django's built-in test framework, test client, and fixtures let you verify models, views, and forms with confidence before shipping.
Django Quick Reference
A cheat-sheet style tour of the manage.py commands, ORM syntax, and settings you reach for most often when building and shipping a Django project.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics