Skip to content

Django + Nginx

Deploy a Django application with Gunicorn WSGI server and Nginx reverse proxy.

Overview

This example demonstrates:

  • Package installation for multiple services
  • Python virtualenv creation and management
  • Jinja2 template rendering for configurations
  • Systemd service creation
  • File permissions and ownership
  • Nginx site configuration

Architecture

                    ┌─────────────┐
    HTTP/443   ────▶│    Nginx    │
                    └──────┬──────┘
                           │ Unix Socket
                    ┌──────▼──────┐
                    │   Gunicorn  │
                    └──────┬──────┘
                    ┌──────▼──────┐
                    │   Django    │
                    └─────────────┘

Files

examples/django_nginx/
├── deploy.py
├── templates/
│   ├── nginx.conf.j2
│   ├── gunicorn.service.j2
│   └── settings_local.py.j2
└── README.md

Configuration

Edit constants in deploy.py:

APP_NAME = "myapp"
APP_DIR = "/opt/myapp"
APP_USER = "www-data"
APP_GROUP = "www-data"
DOMAIN = "example.com"
PYTHON_VERSION = "3.11"
WORKERS = 4

Usage

# Preview changes
python examples/django_nginx/deploy.py --dry-run

# Deploy locally
sudo python examples/django_nginx/deploy.py

# Deploy to remote host
python examples/django_nginx/deploy.py \
    --remote ubuntu@web.example.com \
    --sudo-password

What Gets Deployed

1. System Packages

s.pkgs_install(
    "nginx",
    "python3",
    "python3-pip",
    "python3-venv",
    "python3-dev",
    "build-essential",
    "libpq-dev"  # For psycopg2
)

2. Directory Structure

/opt/myapp/
├── venv/           # Python virtualenv
├── app/            # Django project
├── static/         # Collected static files
├── media/          # User uploads
└── logs/           # Application logs

3. Python Environment

run(f"python3 -m venv {APP_DIR}/venv")
run(f"{APP_DIR}/venv/bin/pip install --upgrade pip wheel")
run(f"{APP_DIR}/venv/bin/pip install gunicorn django psycopg2-binary")

4. Gunicorn Service

/etc/systemd/system/myapp.service:

[Unit]
Description=myapp Gunicorn Service
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp/app
ExecStart=/opt/myapp/venv/bin/gunicorn \
    --workers 4 \
    --bind unix:/run/myapp.sock \
    myapp.wsgi:application
Restart=always

[Install]
WantedBy=multi-user.target

5. Nginx Configuration

/etc/nginx/sites-available/myapp:

upstream myapp {
    server unix:/run/myapp.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com;

    location /static/ {
        alias /opt/myapp/static/;
    }

    location /media/ {
        alias /opt/myapp/media/;
    }

    location / {
        proxy_pass http://myapp;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Templates

nginx.conf.j2

upstream {{ app_name }} {
    server unix:/run/{{ app_name }}.sock fail_timeout=0;
}

server {
    listen 80;
    server_name {{ domain }};

    client_max_body_size 10M;

    location /static/ {
        alias {{ app_dir }}/static/;
        expires 30d;
    }

    location /media/ {
        alias {{ app_dir }}/media/;
    }

    location / {
        proxy_pass http://{{ app_name }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

gunicorn.service.j2

[Unit]
Description={{ app_name }} Gunicorn Service
After=network.target

[Service]
User={{ user }}
Group={{ group }}
WorkingDirectory={{ app_dir }}/app
EnvironmentFile=/etc/{{ app_name }}/env
ExecStart={{ app_dir }}/venv/bin/gunicorn \
    --workers {{ workers }} \
    --bind unix:/run/{{ app_name }}.sock \
    {{ app_name }}.wsgi:application
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Key Code Sections

Install Packages

def install_packages():
    packages = [
        "nginx",
        "python3", "python3-pip", "python3-venv",
        "python3-dev", "build-essential",
        "libpq-dev",  # PostgreSQL client
    ]
    fscm.s.pkgs_install(*packages, sudo=True)

Setup Virtualenv

def setup_virtualenv():
    venv_path = f"{APP_DIR}/venv"

    if not Path(f"{venv_path}/bin/python").exists():
        run(f"python3 -m venv {venv_path}")

    run(f"{venv_path}/bin/pip install --upgrade pip wheel")
    run(f"{venv_path}/bin/pip install -r {APP_DIR}/requirements.txt")

Configure Nginx

def configure_nginx():
    config = template(
        "templates/nginx.conf.j2",
        app_name=APP_NAME,
        domain=DOMAIN,
        app_dir=APP_DIR
    )

    file(f"/etc/nginx/sites-available/{APP_NAME}", config, mode="0644")
    run(f"ln -sf /etc/nginx/sites-available/{APP_NAME} /etc/nginx/sites-enabled/")
    run("rm -f /etc/nginx/sites-enabled/default")

    # Test and reload
    run("nginx -t", sudo=True)
    run("systemctl reload nginx", sudo=True)

Extending

Add SSL with Certbot

def setup_ssl():
    s.pkgs_install("certbot", "python3-certbot-nginx")
    run(f"certbot --nginx -d {DOMAIN} --non-interactive --agree-tos -m admin@{DOMAIN}")

Add Celery Worker

from fscm.modules import systemd

def setup_celery():
    systemd.simple_service(
        name=f"{APP_NAME}-celery",
        exec_start=f"{APP_DIR}/venv/bin/celery -A {APP_NAME} worker -l info",
        user=APP_USER,
        working_dir=f"{APP_DIR}/app",
        env_file=f"/etc/{APP_NAME}/env"
    )

Troubleshooting

Check Service Status

systemctl status myapp
journalctl -u myapp -f

Test Gunicorn Directly

cd /opt/myapp/app
/opt/myapp/venv/bin/gunicorn --bind 0.0.0.0:8000 myapp.wsgi:application

Check Nginx Logs

tail -f /var/log/nginx/error.log
tail -f /var/log/nginx/access.log