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 |
โ 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 |
๐ 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! ๐๐