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
- Deploying With Gunicorn and Nginx
- Static and Media Files in Production
- Settings and Multi-Environment
- Linux SSH basics — Developer workflow on Linux
Target Architecture
Internet → Nginx (443) → Gunicorn (127.0.0.1:8000) → Django
↓
/static/ /media/ (filesystem)
↓
MySQLStep 1: Production Settings Checklist
config/settings/production.py (no secrets in file):
/etc/hello_django/env (mode 600, owned by deploy user):
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-passwordRun python manage.py check --deploy before go-live.
Step 2: Server Preparation
On Ubuntu/Debian VPS:
sudo apt update
sudo apt install -y python3-venv python3-dev nginx mysql-server
sudo mysql_secure_installationCreate database and user — MySQL on Linux.
Deploy user and app directory:
sudo adduser --disabled-password deploy
sudo mkdir -p /var/www/hello_django
sudo chown deploy:deploy /var/www/hello_djangoClone repo or rsync project to /var/www/hello_django/app.
Step 3: Install App and Migrate
As deploy:
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 createsuperuserAdd logs/ to project root for file logging — Error Handling and Logging.
Step 4: Gunicorn systemd Unit
/etc/systemd/system/hello_django.service:
sudo systemctl daemon-reload
sudo systemctl enable --now hello_django
sudo systemctl status hello_djangoStep 5: Nginx Site
/etc/nginx/sites-available/hello_django:
Enable TLS with Certbot — HTTPS and Let's Encrypt.
sudo ln -s /etc/nginx/sites-available/hello_django /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxStep 6: Deploy Script
scripts/deploy.sh on server:
#!/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
curl -I https://example.com/
curl -I https://example.com/static/admin/css/base.css
curl -u admin:password https://example.com/admin/ -ICheck logs on failure:
journalctl -u hello_django -n 50 --no-pager
sudo tail -n 50 /var/log/nginx/error.logVerification Checklist
-
DEBUG=False;check --deployclean - MySQL migrations applied; app connects
-
collectstaticserved 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.