Skip to content

deployment

docisit edited this page Jul 27, 2026 · 2 revisions

🚀 Deployment Guide

Production deployment on a Linux server with Nginx, SSL (Let's Encrypt), Docker, and PM2.


📋 Deployment Overview

ITG Media App is designed to run on a Linux server (Ubuntu 22.04/24.04 LTS recommended). This guide covers the full production deployment including SSL, domain configuration, and process management.

⚠️ Platform Note: ITG Media App has been tested on Linux servers only. Windows Server deployment has not been tested. The live instance at https://donoconnor.com runs on Ubuntu Linux.


🎯 Deployment Architecture

Internet
   │
   ▼
Cloudflare DNS (Optional — DDoS protection, CDN)
   │
   ▼
Your Server (Ubuntu 22.04/24.04)
   │
   ├── Nginx (443) ─── Reverse Proxy + SSL
   │     │
   │     ├── / → Next.js (localhost:3000)
   │     ├── /api/* → Django Gunicorn (localhost:8000)
   │     ├── /ws/* → Daphne WebSocket (localhost:8001)
   │     ├── /admin/* → Django (IP whitelisted)
   │     └── vdo.yourdomain.com → LiveKit (localhost:7880)
   │
   ├── PM2 Process Manager
   │     ├── mediasite-django (Gunicorn)
   │     ├── mediasite-daphne
   │     └── mediasite-nextjs
   │
   ├── Docker Containers
   │     ├── LiveKit Server
   │     ├── LiveKit Egress
   │     ├── LiveKit Ingress
   │     └── Avatar Agent (optional)
   │
   ├── PostgreSQL (localhost:5432)
   │
   └── Redis (localhost:6379)

🖥️ Server Requirements

Resource Minimum Recommended
CPU 2 cores 4+ cores
RAM 4 GB 8+ GB
Storage 40 GB SSD 80+ GB SSD
Bandwidth 1 TB/month Unlimited
OS Ubuntu 22.04/24.04 LTS Ubuntu 24.04 LTS

💡 If running the AI features (Ollama), add at least 8 GB RAM and 20 GB storage for models.


📦 Option A: Docker Production Stack

The simplest production deployment — everything in containers.

1. Clone & Configure

git clone https://github.com/docisit/itg-media-engine.git
cd itg-media-engine

# Configure environment
cp .env.example .env
# Edit .env with your production values

2. Create .env.docker for Production Secrets

# .env.docker
POSTGRES_PASSWORD=<strong-database-password>
REDIS_PASSWORD=
SECRET_KEY=<django-secret-key>
# ... (all other required variables)

3. Start Production Stack

docker compose -f docker-compose.yml up -d

4. Run Migrations & Create Admin

docker exec mediasite-django python manage.py migrate
docker exec -it mediasite-django python manage.py createsuperuser
docker exec mediasite-django python manage.py collectstatic --noinput

🖥️ Option B: PM2 Bare Metal (Recommended for VPS)

Full control over each service with PM2 process management.

Step 1: Initial Server Setup

# Update system
sudo apt update && sudo apt upgrade -y

# Install essentials
sudo apt install -y curl wget git build-essential nginx certbot python3-certbot-nginx

# Install Node.js 20
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Install Python 3.11+
sudo apt install -y python3 python3-pip python3-venv

# Install PostgreSQL
sudo apt install -y postgresql postgresql-client

# Install Redis
sudo apt install -y redis-server

# Install Docker (for LiveKit)
sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable docker
sudo systemctl start docker

Step 2: Deploy Application

Follow the Installation Guide for manual setup (Option B), then continue with this page for SSL, domain, and Nginx configuration.

Step 3: Configure Nginx

Main Site Config

Create /etc/nginx/sites-available/yourdomain.com:

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

# Main HTTPS server
server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    # SSL Certificates (Certbot adds these)
    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Client upload size (for media uploads)
    client_max_body_size 500M;

    # Next.js Frontend
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
        proxy_read_timeout 86400;
    }

    # Django API
    location /api/ {
        proxy_pass http://127.0.0.1:8000;
        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;
    }

    # Django Admin (protected at your custom ADMIN_URL)
    location /admin/ {
        proxy_pass http://127.0.0.1:8000;
        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;
    }

    # Daphne WebSockets
    location /ws/ {
        proxy_pass http://127.0.0.1:8001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
        proxy_read_timeout 86400;
    }

    # Static files
    location /static/ {
        alias /home/deploy/itg-media-engine/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Media uploads
    location /media/ {
        alias /home/deploy/itg-media-engine/media/;
        expires 7d;
    }
}

Enable Site

# Enable the site
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Step 4: SSL with Let's Encrypt

# Get SSL certificate (auto-configures Nginx)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Test auto-renewal
sudo certbot renew --dry-run

# Certbot auto-renews via systemd timer — verify it's active
sudo systemctl status certbot.timer

Step 5: LiveKit Subdomain

Set up a separate subdomain (e.g., vdo.yourdomain.com) for LiveKit WebRTC traffic. See the LiveKit Setup page for the full Nginx config.

# Create LiveKit Nginx config
sudo nano /etc/nginx/sites-available/vdo.yourdomain.com

# Enable and get SSL
sudo ln -s /etc/nginx/sites-available/vdo.yourdomain.com /etc/nginx/sites-enabled/
sudo certbot --nginx -d vdo.yourdomain.com

Step 6: Firewall Configuration

# UFW (Ubuntu Firewall)
sudo ufw allow 22/tcp      # SSH
sudo ufw allow 80/tcp      # HTTP
sudo ufw allow 443/tcp     # HTTPS
sudo ufw allow 7880/tcp    # LiveKit API
sudo ufw allow 7881/tcp    # WebRTC TCP fallback
sudo ufw allow 7882/udp    # WebRTC ICE
sudo ufw allow 50000:60000/udp  # WebRTC media
sudo ufw enable

Step 7: Start Services

# Install PM2
npm install -g pm2

# Start services (from project root)
pm2 start ecosystem.config.js

# Save for auto-restart on boot
pm2 save

# Enable PM2 startup
pm2 startup
# Follow the printed command to complete setup

# Check status
pm2 list
pm2 logs

🔧 PM2 Ecosystem Config

Create ecosystem.config.js in the project root:

module.exports = {
  apps: [
    {
      name: 'mediasite-django',
      script: '.venv/bin/gunicorn',
      args: 'backend.wsgi:application --bind 127.0.0.1:8000 --workers 4 --timeout 120 --access-logfile /var/log/mediasite/gunicorn-access.log --error-logfile /var/log/mediasite/gunicorn-error.log',
      cwd: '/home/deploy/itg-media-engine',
      env: {
        DJANGO_ENV: 'production',
      },
      autorestart: true,
      max_restarts: 10,
      min_uptime: '10s',
    },
    {
      name: 'mediasite-daphne',
      script: '.venv/bin/daphne',
      args: '-b 127.0.0.1 -p 8001 backend.asgi:application',
      cwd: '/home/deploy/itg-media-engine',
      autorestart: true,
      max_restarts: 10,
    },
    {
      name: 'mediasite-nextjs',
      script: 'node_modules/.bin/next',
      args: 'start -p 3000',
      cwd: '/home/deploy/itg-media-engine/frontend',
      autorestart: true,
      max_restarts: 10,
    },
  ],
};

🐳 Docker Production Stack

Reference docker-compose.yml for full containerized deployment:

# Key services defined in docker-compose.yml:
# - nginx (production reverse proxy)
# - nextjs (Next.js frontend)
# - django (Django + Gunicorn)
# - daphne (Django Channels WebSocket)
# - postgres (PostgreSQL 16)
# - redis (Redis 7)

Build and start:

docker compose -f docker-compose.yml build
docker compose -f docker-compose.yml up -d

🔄 Updating & Maintenance

Update Application Code

# Pull latest changes
cd itg-media-engine
git pull origin main

# Backend
source .venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput

# Frontend
cd frontend
npm install
npm run build

# Restart services
pm2 restart all

Database Backup

# PostgreSQL dump
pg_dump -U media_user -h localhost media_db > backup_$(date +%Y%m%d).sql

# Compress
gzip backup_$(date +%Y%m%d).sql

# Store off-server (S3, rsync, etc.)

Log Rotation

# PM2 handles its own log rotation
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

Monitor Disk Space

# Check disk usage
df -h

# Clean Docker images
docker system prune -a

# Clean old PM2 logs
pm2 flush

✅ Deployment Checklist

  • Server updated (apt update && apt upgrade)
  • Node.js 20+ installed
  • Python 3.11+ installed
  • PostgreSQL 16+ installed and running
  • Redis 7+ installed and running
  • Docker installed (for LiveKit)
  • Application cloned and configured
  • .env configured with production values
  • DEBUG=False
  • ALLOWED_HOSTS set correctly
  • ADMIN_URL changed from default
  • ADMIN_IP_WHITELIST set
  • Migrations run
  • Static files collected
  • Admin user created
  • Nginx configured
  • SSL certificates installed (Let's Encrypt)
  • LiveKit subdomain configured
  • Firewall configured (UFW)
  • PM2 services running
  • PM2 startup enabled
  • All services verified (see Installation Verification)
  • Automated backups configured

⏭️ Next Steps


← Back to Wiki Home

Clone this wiki locally