Module 15: Django Deployment & Production
🎯 What You Will Learn in This Module
By the end of this tutorial, you will:
- Understand the critical differences between development and production
- Configure security headers to protect your application from common attacks
- Set up PostgreSQL with persistent connections for production loads
- Serve static files efficiently using WhiteNoise (no Nginx required)
- Configure Gunicorn as your production WSGI server
- Containerize your application with a Dockerfile
- Manage environment variables for secrets and configuration
- Set up production logging to track errors
- Know how to deploy to Render, Railway, or any cloud platform
📖 1. Development vs. Production
Your local machine is not the internet. Before deploying, you must change several settings that keep you safe during development but would be dangerous in production.
| Setting | Development | Production |
|---|---|---|
DEBUG | True (detailed error pages) | False (generic errors) |
SECRET_KEY | Hardcoded or default | Environment variable |
ALLOWED_HOSTS | ['localhost'] | Your actual domain |
DATABASE | SQLite (file-based) | PostgreSQL (server-based) |
Static Files | Served automatically by Django | Served by WhiteNoise or Nginx |
Email | Console backend (prints to terminal) | SMTP (real email service) |
HTTPS | HTTP only | Enforced with security headers |
🔐 2. Production Settings
Create a separate settings file for production. This keeps your development settings untouched.
1# myproject/settings/production.py 2 3from .base import * # Import base settings 4import os 5 6# ==================== CORE SECURITY ==================== 7 8DEBUG = False 9 10ALLOWED_HOSTS = [ 11 'myblog.com', 12 'www.myblog.com', 13 'myapp.onrender.com', # Add your platform's domain too 14] 15 16# ==================== HTTPS & SECURITY HEADERS ==================== 17 18SECURE_SSL_REDIRECT = True # Redirect all HTTP to HTTPS 19SESSION_COOKIE_SECURE = True # Only send session cookies over HTTPS 20CSRF_COOKIE_SECURE = True # Only send CSRF cookies over HTTPS 21SECURE_BROWSER_XSS_FILTER = True # Enable browser XSS filtering 22SECURE_CONTENT_TYPE_NOSNIFF = True # Prevent MIME-type sniffing 23X_FRAME_OPTIONS = 'DENY' # Prevent clickjacking (embedding in iframes) 24 25# HSTS (HTTP Strict Transport Security) 26# Tells browsers to ALWAYS use HTTPS for your domain 27SECURE_HSTS_SECONDS = 31536000 # 1 year 28SECURE_HSTS_INCLUDE_SUBDOMAINS = True # Apply to all subdomains 29SECURE_HSTS_PRELOAD = True # Allow browser preload lists 30 31# ==================== DATABASE ==================== 32 33DATABASES = { 34 'default': { 35 'ENGINE': 'django.db.backends.postgresql', 36 'NAME': os.environ['DB_NAME'], 37 'USER': os.environ['DB_USER'], 38 'PASSWORD': os.environ['DB_PASSWORD'], 39 'HOST': os.environ['DB_HOST'], 40 'PORT': '5432', 41 'CONN_MAX_AGE': 600, # Persistent connections: 10 minutes 42 } 43} 44 45# ==================== STATIC FILES (WhiteNoise) ==================== 46 47MIDDLEWARE = [ 48 'django.middleware.security.SecurityMiddleware', 49 'whitenoise.middleware.WhiteNoiseMiddleware', # Must be after SecurityMiddleware 50 'django.contrib.sessions.middleware.SessionMiddleware', 51 'django.middleware.common.CommonMiddleware', 52 'django.middleware.csrf.CsrfViewMiddleware', 53 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 'django.contrib.messages.middleware.MessageMiddleware', 55 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56] 57 58STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 59 60# ==================== LOGGING ==================== 61 62LOGGING = { 63 'version': 1, 64 'disable_existing_loggers': False, 65 'handlers': { 66 'file': { 67 'level': 'ERROR', 68 'class': 'logging.FileHandler', 69 'filename': '/var/log/django/error.log', 70 }, 71 }, 72 'loggers': { 73 'django': { 74 'handlers': ['file'], 75 'level': 'ERROR', 76 'propagate': True, 77 }, 78 }, 79} 80 81# ==================== EMAIL (Production SMTP) ==================== 82 83EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 84EMAIL_HOST = 'smtp.sendgrid.net' # Or Gmail, Mailgun, AWS SES 85EMAIL_PORT = 587 86EMAIL_USE_TLS = True 87EMAIL_HOST_USER = 'apikey' # SendGrid uses 'apikey' as username 88EMAIL_HOST_PASSWORD = os.environ['SENDGRID_API_KEY'] 89DEFAULT_FROM_EMAIL = 'noreply@myblog.com'
Security Headers Explained
| Header | What It Protects Against |
|---|---|
SECURE_SSL_REDIRECT | Man-in-the-middle attacks on HTTP |
SESSION_COOKIE_SECURE | Session hijacking via HTTP sniffing |
CSRF_COOKIE_SECURE | CSRF token theft via HTTP |
SECURE_BROWSER_XSS_FILTER | Cross-site scripting (XSS) |
SECURE_CONTENT_TYPE_NOSNIFF | MIME confusion attacks |
X_FRAME_OPTIONS = 'DENY' | Clickjacking attacks |
SECURE_HSTS_SECONDS | SSL stripping attacks |
🚨 Warning: Only enable
SECURE_SSL_REDIRECTandSECURE_HSTS_*after you have confirmed HTTPS is working. If you enable them without HTTPS configured, users will be unable to access your site.
🦄 3. Gunicorn Configuration
Gunicorn (Green Unicorn) is a production-grade WSGI server that handles multiple concurrent requests. Django's built-in runserver is single-threaded and not secure for production.
Create gunicorn.conf.py in your project root:
1# gunicorn.conf.py 2 3bind = "0.0.0.0:8000" # Bind to all interfaces on port 8000 4workers = 4 # Number of worker processes 5 # Formula: (2 x CPU cores) + 1 6worker_class = "sync" # Synchronous workers (standard) 7worker_connections = 1000 # Max concurrent connections per worker 8timeout = 30 # Kill workers that take >30 seconds 9keepalive = 2 # Keep connections open for 2 seconds 10errorlog = "-" # Log errors to stdout (for Docker/cloud) 11accesslog = "-" # Log access to stdout 12capture_output = True # Capture print statements 13enable_stdio_inheritance = True # Allow stdout/stderr from workers
How Gunicorn works:
┌─────────────┐
│ Nginx or │ ← Reverse proxy (optional but recommended)
│ Cloud LB │ Handles HTTPS, static files, DDoS protection
└──────┬──────┘
│
┌──────▼──────┐
│ Gunicorn │ ← WSGI server
│ Master │ Manages worker processes
│ Process │
└──────┬──────┘
│
┌───┴───┐
▼ ▼ ▼
┌──┐ ┌──┐ ┌──┐
│W1│ │W2│ │W3│ ← Worker processes (each runs your Django app)
└──┘ └──┘ └──┘
🐳 4. Dockerizing Your Application
Docker packages your application with all its dependencies, ensuring it runs identically on any machine.
The Dockerfile
1# Dockerfile 2 3# Use official Python image (slim = smaller size) 4FROM python:3.11-slim 5 6# Set working directory inside the container 7WORKDIR /app 8 9# Prevent Python from writing .pyc files 10ENV PYTHONDONTWRITEBYTECODE=1 11 12# Prevent Python from buffering stdout (important for logs) 13ENV PYTHONUNBUFFERED=1 14 15# Install system dependencies required for psycopg2 16RUN apt-get update && apt-get install -y \ 17 gcc \ 18 libpq-dev \ 19 && rm -rf /var/lib/apt/lists/* 20 21# Install Python dependencies 22COPY requirements.txt . 23RUN pip install --no-cache-dir -r requirements.txt 24 25# Copy the entire project into the container 26COPY . . 27 28# Collect static files (WhiteNoise needs them at runtime) 29RUN python manage.py collectstatic --noinput 30 31# Expose port 8000 to the outside world 32EXPOSE 8000 33 34# Run Gunicorn when the container starts 35CMD ["gunicorn", "--config", "gunicorn.conf.py", "myproject.wsgi:application"]
The .dockerignore File
Prevent unnecessary files from being copied into the container:
1# .dockerignore 2 3venv/ 4__pycache__/ 5*.pyc 6*.pyo 7*.pyd 8.Python 9db.sqlite3 10.env 11.git/ 12.gitignore 13.pytest_cache/ 14.coverage 15htmlcov/ 16media/ # User uploads should not be in the image
📦 5. Requirements & Environment Variables
requirements.txt
Your requirements.txt should include production dependencies:
Django>=4.2,<5.0
djangorestframework
djangorestframework-simplejwt
django-cors-headers
django-filter
python-decouple
Pillow
psycopg2-binary
whitenoise
gunicorn
django-redis
Environment Variables
Never commit secrets to Git. Use environment variables:
1# .env (add this to .gitignore!) 2 3DJANGO_SECRET_KEY=your-very-long-secret-key-here 4DJANGO_DEBUG=False 5DJANGO_SETTINGS_MODULE=myproject.settings.production 6 7DB_NAME=mydb 8DB_USER=postgres 9DB_PASSWORD=strong-db-password 10DB_HOST=db-host-from-provider 11DB_PORT=5432 12 13SENDGRID_API_KEY=your-sendgrid-api-key
🚀 6. Deployment Checklist
Before you deploy, verify every item on this list:
-
DEBUG = Falsein production settings -
SECRET_KEYis loaded from environment variables -
ALLOWED_HOSTSincludes your production domain -
DATABASESpoints to PostgreSQL, not SQLite -
STATIC_ROOTis set andcollectstaticruns successfully -
python manage.py check --deployshows zero issues -
python manage.py testpasses all tests - All migrations are applied:
python manage.py migrate - A superuser exists:
python manage.py createsuperuser -
.envis in.gitignore -
requirements.txtis up to date -
Dockerfilebuilds successfully:docker build -t myapp . - Security headers are configured
☁️ 7. Deploying to Render (Step-by-Step)
Render is a popular, beginner-friendly platform for deploying Django.
Step 1: Push to GitHub
1git init 2git add . 3git commit -m "Production ready" 4git push origin main
Step 2: Create a New Web Service on Render
- Sign up at render.com
- Click New + → Web Service
- Connect your GitHub repository
- Select the repository
Step 3: Configure the Service
| Setting | Value |
|---|---|
| Environment | Docker |
| Branch | main |
| Plan | Free (or Starter for production) |
Step 4: Add Environment Variables
In the Render dashboard, add all variables from your .env file:
DJANGO_SECRET_KEYDJANGO_SETTINGS_MODULEDB_NAME,DB_USER, etc.
Step 5: Create a PostgreSQL Database
- Click New + → PostgreSQL
- Copy the Internal Database URL
- Add it as an environment variable or parse it into
DB_HOST,DB_NAME, etc.
Step 6: Deploy
Click Create Web Service. Render will:
- Build your Docker image
- Run
collectstatic - Start Gunicorn
- Provide you with a live URL
🧪 8. Practice Task — Deploy Your Django App
Task Requirements
Deploy your complete blog application to a cloud platform with:
- PostgreSQL database (not SQLite)
DEBUG = False- Environment variables for all secrets
- Working static files (CSS/JS loads correctly)
- HTTPS enabled (platforms like Render provide this automatically)
- Admin panel accessible at
/admin/
Verification Steps
After deployment, verify these all work:
- Homepage loads without errors
- API endpoints return JSON (e.g.,
/api/posts/) - Admin panel loads and you can log in
- Static files are served (check browser DevTools Network tab)
- You can create a post via the API
-
python manage.py check --deploypasses locally with production settings
❌ Common Mistakes & How to Fix Them
| Mistake | Error / Symptom | Solution |
|---|---|---|
DEBUG = True in production | Detailed error pages expose secrets | Set DEBUG = False |
ALLOWED_HOSTS empty | 400 Bad Request on every page | Add your domain to the list |
SECRET_KEY hardcoded | Security breach if repo is public | Use environment variables |
| SQLite in production | OperationalError: database is locked | Switch to PostgreSQL |
collectstatic not run | 404 on CSS/JS files | Run collectstatic during build |
| WhiteNoise not in middleware | Static files don't load | Add WhiteNoiseMiddleware to MIDDLEWARE |
CONN_MAX_AGE too high | Database connection errors | Set to 600 (10 minutes) max |
SECURE_SSL_REDIRECT without HTTPS | Infinite redirect loops | Only enable after HTTPS is confirmed working |
Missing EXPOSE 8000 in Dockerfile | Platform cannot route traffic | Always expose the port Gunicorn binds to |
gunicorn.conf.py syntax error | Container crashes on start | Validate Python syntax in the config file |
.env committed to Git | Secrets exposed | Add .env to .gitignore immediately |
✅ Module 15 Summary
| Concept | Key Takeaway |
|---|---|
DEBUG = False | Required for production. Never expose debug pages. |
ALLOWED_HOSTS | Whitelist of domains that can serve your app. |
SECURE_SSL_REDIRECT | Forces all traffic to HTTPS. |
SECURE_HSTS_SECONDS | Tells browsers to always use HTTPS. |
SESSION_COOKIE_SECURE | Cookies only transmitted over HTTPS. |
CONN_MAX_AGE | Persistent database connections for performance. |
| WhiteNoise | Serves static files without Nginx. |
collectstatic | Gathers all static files into STATIC_ROOT. |
| Gunicorn | Production WSGI server for handling concurrent requests. |
workers = (2 x CPU) + 1 | Optimal worker count formula. |
| Docker | Containerizes your app for consistent deployment. |
| Environment Variables | Secure way to manage secrets and config. |
python manage.py check --deploy | Django's built-in production readiness checker. |
🎓 Course Complete!
Congratulations! You have completed the Complete Django Course. Over 15 modules, you have built a production-ready blog application with:
- ✅ Custom user authentication with email login
- ✅ Database design with relationships (ForeignKey, ManyToMany, OneToOne)
- ✅ Django Admin customization with actions and inlines
- ✅ Function-Based and Class-Based Views
- ✅ Forms with validation and honeypot spam protection
- ✅ REST API with serializers, ViewSets, and routers
- ✅ JWT authentication with custom claims
- ✅ Email verification and password reset flows
- ✅ Middleware, signals, query optimization, and caching
- ✅ Comprehensive unit and API testing
- ✅ Production deployment with Docker, Gunicorn, and security headers
What to build next:
- A real-time chat feature using Django Channels
- An e-commerce platform with Stripe payments
- A social network with follow/unfollow and feeds
- A job board with search and filtering
- A SaaS application with multi-tenancy
Keep building. Keep learning. 🐍🚀
Your Django application is live on the internet. Welcome to the world of production Python web development! 🌐🚀