Project: Production Deployment

Introduction

This capstone walks through deploying a Django project to a Linux VPS: environment variables, MySQL, collectstatic, Gunicorn behind Nginx, systemd, migrations, and a smoke test. It ties together Gunicorn and Nginx, Docker and CI, and Security into one checklist you can reuse for blog or API projects.

Prerequisites

Target Architecture

text
Internet → Nginx (443) → Gunicorn (127.0.0.1:8000) → Django

         /static/  /media/  (filesystem)

              MySQL

Step 1: Production Settings Checklist

config/settings/production.py (no secrets in file):

/etc/hello_django/env (mode 600, owned by deploy user):

bash
DJANGO_SETTINGS_MODULE=config.settings.production
SECRET_KEY=replace-with-long-random-string
ALLOWED_HOSTS=example.com,www.example.com
DB_NAME=hello_django
DB_USER=django_user
DB_PASSWORD=strong-db-password

Run python manage.py check --deploy before go-live.

Step 2: Server Preparation

On Ubuntu/Debian VPS:

bash
sudo apt update
sudo apt install -y python3-venv python3-dev nginx mysql-server
sudo mysql_secure_installation

Create database and user — MySQL on Linux.

Deploy user and app directory:

bash
sudo adduser --disabled-password deploy
sudo mkdir -p /var/www/hello_django
sudo chown deploy:deploy /var/www/hello_django

Clone repo or rsync project to /var/www/hello_django/app.

Step 3: Install App and Migrate

As deploy:

bash
cd /var/www/hello_django/app
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt gunicorn mysqlclient
set -a && source /etc/hello_django/env && set +a
python manage.py migrate --noinput
python manage.py collectstatic --noinput
python manage.py createsuperuser

Add logs/ to project root for file logging — Error Handling and Logging.

Step 4: Gunicorn systemd Unit

/etc/systemd/system/hello_django.service:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now hello_django
sudo systemctl status hello_django

Step 5: Nginx Site

/etc/nginx/sites-available/hello_django:

Enable TLS with Certbot — HTTPS and Let's Encrypt.

bash
sudo ln -s /etc/nginx/sites-available/hello_django /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 6: Deploy Script

scripts/deploy.sh on server:

bash
#!/usr/bin/env bash
set -euo pipefail
cd /var/www/hello_django/app
source .venv/bin/activate
set -a && source /etc/hello_django/env && set +a
git pull origin main
pip install -r requirements.txt
python manage.py migrate --noinput
python manage.py collectstatic --noinput
sudo systemctl restart hello_django
curl -fsS https://example.com/health/ || exit 1
echo "Deploy OK"

Add a simple /health/ view returning {"status": "ok"} for monitoring.

Step 7: Post-Deploy Smoke Test

bash
curl -I https://example.com/
curl -I https://example.com/static/admin/css/base.css
curl -u admin:password https://example.com/admin/ -I

Check logs on failure:

bash
journalctl -u hello_django -n 50 --no-pager
sudo tail -n 50 /var/log/nginx/error.log

Verification Checklist

  • DEBUG=False; check --deploy clean
  • MySQL migrations applied; app connects
  • collectstatic served by Nginx
  • HTTPS works; cookies marked Secure
  • systemd restarts Gunicorn on failure
  • Deploy script runs migrate + collectstatic

FAQ

Docker instead of bare metal?

Use Docker and CI compose with same env vars.

Zero-downtime deploy?

Blue/green Gunicorn sockets or rolling restart—advanced ops.

Redis for cache/sessions?

Point CACHES and SESSION_ENGINE to Redis — Caching.

502 after deploy?

Gunicorn down, wrong socket, or SELinux blocking—see Troubleshooting.

Compare Flask deploy?

Same Nginx + Gunicorn pattern — Flask Gunicorn.