docs

Django Deployment Guide

Complete Django deployment guide: server setup, Docker, Nginx, Gunicorn, SSL certificates, PostgreSQL, and monitoring. Step-by-step from development to production.

TL;DR

This guide covers the full Django deployment stack I use for client projects: Ubuntu server on DigitalOcean or Hetzner, Docker containers for application isolation, Gunicorn as the WSGI server, Nginx as the reverse proxy and static file server, PostgreSQL for the database, SSL via Let's Encrypt, and monitoring with uptime checks and error tracking. Monthly hosting cost: EUR 10-50 depending on traffic. The same stack handles 10,000+ daily users reliably.

Production Stack Overview

A production Django deployment has several layers, each with a specific role. Here is the full stack I deploy for client projects and why each component is chosen.

The stack:

  • Server: Ubuntu 24.04 LTS on DigitalOcean (EUR 6-24/month) or Hetzner (EUR 4-15/month). Hetzner offers significantly better price-to-performance for European projects. A 2 vCPU / 4GB RAM server handles most applications comfortably.
  • Application server: Gunicorn — a Python WSGI HTTP server that runs your Django application. Configured with 2-4 workers per CPU core. Handles concurrent requests efficiently without the complexity of alternatives like uWSGI.
  • Reverse proxy: Nginx sits in front of Gunicorn, handling SSL termination, static file serving, request buffering, and basic rate limiting. Nginx serves static files 10-50x faster than Django and protects Gunicorn from slow clients.
  • Database: PostgreSQL 16 — the standard for Django projects. Runs on the same server for small projects (up to 50,000 daily users) or as a managed service (DigitalOcean Managed Database, EUR 15/month) for projects that need automated backups and failover.
  • Cache: Redis for session storage, Celery task queue, and application caching. Runs alongside the application on the same server. Memory usage is typically 50-200MB.
  • Task queue: Celery with Redis broker for background tasks — sending emails, processing webhooks, generating reports, running scheduled jobs.

This stack is intentionally simple. Every component is well-documented, widely used, and easy to debug. I avoid over-engineering with Kubernetes or microservices unless the project genuinely requires horizontal scaling.

Docker Setup for Django

Docker provides consistent environments across development and production, simplifies deployments, and makes rollbacks trivial.

Container architecture:

  • Web container: Runs Gunicorn with the Django application. Based on Python 3.12 slim image. Includes only production dependencies — no dev tools, test frameworks, or debugging packages.
  • Celery worker container: Same Django codebase but runs the Celery worker process instead of Gunicorn. Shares the same Docker image as the web container with a different entrypoint command.
  • Celery Beat container: Runs the Celery Beat scheduler for periodic tasks (daily reports, cleanup jobs, sync schedules). Lightweight — one instance only, no scaling needed.
  • Nginx container: Serves static files and proxies requests to the web container. Based on the official Nginx Alpine image for minimal footprint.
  • PostgreSQL container: Database server. Data persisted via Docker volume mapped to the host filesystem. For production, I increasingly prefer managed database services over self-hosted PostgreSQL containers for automated backups and updates.
  • Redis container: Cache and message broker. Minimal configuration needed. Data persistence optional — configured based on whether cached data needs to survive restarts.

Docker Compose orchestration: All containers are defined in a single docker-compose.yml file. Environment variables are loaded from a .env file (never committed to version control). Health checks ensure containers restart automatically on failure. A single docker compose up -d command deploys the entire stack.

Image optimisation: Multi-stage builds keep production images small (typically 150-250MB). Dependencies are installed in a build stage and only the compiled packages are copied to the runtime stage. This reduces attack surface and speeds up deployments.

Nginx Configuration & SSL

Nginx handles the interface between the internet and your application — SSL encryption, static files, security headers, and request routing.

SSL with Let's Encrypt: Free SSL certificates via Certbot with automatic renewal every 60 days. Initial setup takes 5 minutes. Nginx is configured for TLS 1.2 and 1.3 only (older protocols disabled). HSTS header ensures browsers always use HTTPS. SSL Labs rating: A+ with proper configuration.

Static and media file serving: Nginx serves static files (CSS, JavaScript, images) directly from the filesystem without touching Django. collectstatic gathers all static files into a single directory during deployment. Media files (user uploads) are served from a separate directory with appropriate cache headers. For high-traffic sites, static files can be moved to a CDN (Cloudflare, BunnyCDN) with Nginx as the origin.

Security headers: Every response includes security headers configured in Nginx: Content-Security-Policy (prevents XSS), X-Frame-Options (prevents clickjacking), X-Content-Type-Options (prevents MIME sniffing), Referrer-Policy, and Permissions-Policy. These headers are often overlooked but are essential for production security.

Rate limiting: Nginx rate limiting protects against brute force attacks and abusive traffic. Login endpoints are limited to 5 requests per minute per IP. API endpoints are limited based on the expected usage pattern. Rate-limited requests receive a 429 response with a Retry-After header.

Gzip compression: Text-based responses (HTML, CSS, JavaScript, JSON) are compressed with gzip, reducing transfer size by 60-80%. This improves page load times and reduces bandwidth costs. Images and other binary files are not compressed (they are already compressed in their native format).

Production Security Checklist

A comprehensive security checklist I follow before any Django project goes live.

Django settings:

  • DEBUG = False — never True in production. Debug mode exposes stack traces, settings, and SQL queries to users.
  • SECRET_KEY — unique, random, 50+ characters. Loaded from environment variable, never hardcoded.
  • ALLOWED_HOSTS — explicitly list your domain names. Never use ["*"] in production.
  • SECURE_SSL_REDIRECT = True — force all HTTP traffic to HTTPS.
  • SESSION_COOKIE_SECURE = True and CSRF_COOKIE_SECURE = True — cookies only sent over HTTPS.
  • SECURE_HSTS_SECONDS = 31536000 — tell browsers to always use HTTPS for your domain.
  • Database credentials in environment variables, not in settings.py.

Server hardening:

  • SSH key authentication only — password login disabled
  • Firewall (UFW) — only ports 22 (SSH), 80 (HTTP), and 443 (HTTPS) open
  • Automatic security updates enabled (unattended-upgrades)
  • Non-root user for application processes
  • Fail2ban for SSH brute force protection

Database security:

  • PostgreSQL listens on localhost only (not exposed to the internet)
  • Application database user has minimal permissions (no SUPERUSER, no CREATE DATABASE)
  • Automated daily backups to external storage (DigitalOcean Spaces, Backblaze B2)
  • Backup restoration tested monthly

Application security:

  • CSRF protection enabled on all forms (Django default)
  • SQL injection prevented by ORM usage (no raw SQL unless absolutely necessary)
  • File upload validation — type, size, and content checks
  • Rate limiting on authentication endpoints
  • Dependency vulnerability scanning with pip audit in CI pipeline

Monitoring & Maintenance

Deploying the application is half the job. Monitoring ensures it stays running and you catch problems before users report them.

Uptime monitoring: External health check service (UptimeRobot free tier or Better Stack) pings the application every 60 seconds. If two consecutive checks fail, an alert is sent via Telegram and email. The health check endpoint (/health/) verifies database connectivity and Redis availability — it does not just return 200 blindly.

Error tracking: Sentry captures unhandled exceptions with full stack traces, request data, and user context. Errors are grouped by type and frequency. New errors trigger an immediate Telegram notification. The free Sentry tier handles up to 5,000 events per month, which is sufficient for most projects.

Log management: Application logs are written in structured JSON format to stdout (captured by Docker). Logs include request ID for tracing, user ID, endpoint, response time, and status code. For debugging, logs are queryable via docker compose logs with grep. For larger deployments, logs are shipped to a centralised service (Grafana Loki or Papertrail).

Performance monitoring: Django Debug Toolbar in development. In production, middleware tracks response times per endpoint. Slow queries (over 100ms) are logged with the full SQL and explain plan. A weekly performance report flags endpoints that have degraded.

Automated maintenance:

  • Database backups: daily automated backup to object storage, 30-day retention
  • SSL certificate renewal: automated via Certbot cron job, verified monthly
  • Security updates: unattended-upgrades for OS packages, monthly review of Python dependency updates
  • Disk space monitoring: alert when disk usage exceeds 80%
  • Docker image cleanup: weekly removal of unused images and containers to reclaim disk space

Deployment process: Git push to main branch triggers a GitHub Actions pipeline: run tests, build Docker image, push to container registry, SSH to server, pull new image, restart containers with zero-downtime rolling update. Total deployment time: 3-5 minutes from push to live.

Frequently Asked Questions

How much does hosting a Django application cost per month?

A standard Django application with PostgreSQL and Redis runs on a EUR 6-12/month DigitalOcean droplet or EUR 4-8/month Hetzner VPS. Add EUR 3-5/month for automated backups. Total: EUR 10-20/month for applications serving up to 50,000 daily users. High-traffic applications (100,000+ daily users) may need EUR 24-50/month for a larger server or managed database.

Do you use Kubernetes for deployment?

Not for most projects. Docker Compose on a single server handles 95% of the applications I build. Kubernetes adds significant operational complexity (and cost) that is only justified for applications requiring horizontal auto-scaling, multi-region deployment, or microservice orchestration. For a startup or SMB application, Docker Compose is the right choice.

How do you handle zero-downtime deployments?

I use Docker Compose with a rolling update strategy. The new container starts alongside the old one, health checks confirm it is responding correctly, and then the old container is stopped. Nginx routes traffic to healthy containers throughout. The entire process takes 10-30 seconds with zero dropped requests.

Need Help Deploying Your Django Project?

I handle the full deployment — server setup, Docker, SSL, monitoring, and CI/CD pipeline. One-time setup from EUR 300.

Get Deployment Help

or message directly: Telegram · Email