Auth, APIs & ProjectPro· 55 min read

Deploying to Production

Ship your app to the real internet — gunicorn + WSGI, DEBUG=False, ALLOWED_HOSTS, environment secrets, PostgreSQL and static files.

What you will learn

  • Make a project production-safe (DEBUG, ALLOWED_HOSTS, secrets)
  • Serve the app with gunicorn/WSGI and a real database
  • Serve static files with WhiteNoise and go live

The dev server is not for production

The runserver command you have used is a development server — convenient, but slow and insecure, and the docs explicitly say never to use it for a live site. Going to production means swapping in production-grade pieces. Here is the cast of characters:

PieceJob
WSGIthe standard "socket" between Python web apps and servers
gunicorna fast production server that runs your Django app via WSGI
Nginxa web server in front that handles incoming traffic / HTTPS
PostgreSQLa real database (replaces the dev-only SQLite file)
WhiteNoise / S3serves your static files efficiently in production

WSGI (Web Server Gateway Interface) is just an agreed-upon way for a Python web app and a web server to talk; Django created a wsgi.py file in your project for exactly this. gunicorn is the server that speaks WSGI and runs your code.

Step 1 — make settings production-safe

Three settings turn a development project into a safe one. Getting these wrong is the most common (and most dangerous) deployment mistake:

The three safety-critical settings
# settings.py
DEBUG = False                          # NEVER True in production
ALLOWED_HOSTS = ["yourdomain.com"]     # only answer for hosts you own
# SECRET_KEY must come from the environment, not the code (next step)

Watch out: With DEBUG = True on a live site, any error page shows your source code, settings and database details to the whole world. Set it to False before you deploy — this is the single biggest security mistake beginners make.

Why each matters:

  • DEBUG = False — hides the detailed error pages (which leak secrets) and shows a plain error instead.
  • ALLOWED_HOSTS — Django refuses requests whose Host header is not in this list, blocking a class of attacks.
  • SECRET_KEY — signs sessions and tokens; if it leaks, attackers can forge them, so it must never sit in your committed code.

Step 2 — secrets in environment variables

An environment variable is a value the operating system holds outside your code, so you can keep secrets (the SECRET_KEY, the database password) out of Git. You read them in settings.py instead of hard-coding them:

Read secrets from the environment, not the code
# settings.py
import os

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]   # read from the environment
DEBUG = os.environ.get("DEBUG", "False") == "True"

# locally you keep these in a .env file (which is git-ignored):
#   DJANGO_SECRET_KEY=super-long-random-string
#   DEBUG=True

Note: The rule: config that changes per environment, and anything secret, lives in environment variables — never in committed code. The .env file holding them must be listed in .gitignore.

Step 3 — a real database (PostgreSQL)

Development uses SQLite (a single file) — fine for learning, but production wants PostgreSQL, a robust multi-user database. You point Django at it in settings, again reading the connection details from the environment:

Switch the database to PostgreSQL
# settings.py
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": os.environ["DB_HOST"],
        "PORT": "5432",
    }
}
# install the driver:  pip install psycopg2-binary

After switching databases you run the same python manage.py migrate you already know — Django builds your tables in PostgreSQL exactly as it did in SQLite. Your models and queries do not change at all; that is the ORM paying off.

Step 4 — serve static files with WhiteNoise

With DEBUG = False, Django stops serving static files itself. WhiteNoise is a small tool that lets gunicorn serve your collected static files efficiently — no separate setup needed. Three lines wire it in:

WhiteNoise serves static files in production
# settings.py
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",   # add right after security
    # ... the rest ...
]
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {"staticfiles": {"BACKEND":
    "whitenoise.storage.CompressedManifestStaticFilesStorage"}}
# install:  pip install whitenoise

Remember the collectstatic command from the static-files lesson — you run it as part of deploying so WhiteNoise has the gathered files to serve.

Step 5 — run it with gunicorn

Locally you typed runserver. In production you start gunicorn instead, pointing it at your project’s WSGI application:

gunicorn runs your app via WSGI
# install and run the production server
pip install gunicorn
gunicorn mysite.wsgi:application --bind 0.0.0.0:8000

Note: Output: [INFO] Starting gunicorn 21.2.0 [INFO] Listening at: http://0.0.0.0:8000 [INFO] Booting worker with pid: 31 gunicorn is now serving your real Django app. In a full setup, Nginx sits in front to handle HTTPS and pass traffic to gunicorn.

The deployment checklist

Tie it all together. A reliable first deploy follows this order:

  1. Set DEBUG = False and fill in ALLOWED_HOSTS with your domain.
  2. Move SECRET_KEY, database details and other secrets into environment variables.
  3. Switch the database to PostgreSQL and run python manage.py migrate.
  4. Add WhiteNoise and run python manage.py collectstatic.
  5. Install and start gunicorn (gunicorn mysite.wsgi:application).
  6. Put it on a host (Render, Railway, Fly.io, a VPS with Nginx, or AWS) and visit your live URL.

Tip: Modern hosts like Render or Railway bundle gunicorn, PostgreSQL and environment variables into a few clicks, so your job is mostly the production-safe settings above. Whatever the host, the checklist is the same — and a live URL is the single most valuable thing you can show an employer.

Q. Which setting is the most dangerous to leave at its development value when you go live?

Answer: DEBUG = True on a live site exposes your source code, settings and database details on every error page. It must be set to False in production — the single biggest deployment security mistake.

✍️ Practice

  1. Rewrite your settings.py to read SECRET_KEY and DEBUG from environment variables and set ALLOWED_HOSTS.
  2. Add WhiteNoise, run collectstatic, and start your app locally with gunicorn mysite.wsgi:application.

🏠 Homework

  1. Deploy your project to a free host (Render or Railway) with DEBUG=False, PostgreSQL and static files working, and share the live URL.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →