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"
)