Problems Deploying Site to Remote Container

>> ubuntu, nginx, whitenoise

| [UPDATED: 2026.04.05]

Static File Serving Problem - Resolution

Problem Description

Django application was not serving static files (CSS, JS, images, fonts) properly in production. Browsers were receiving HTML responses instead of the correct static file content, causing "MIME type" errors and broken styling.

Root Cause Analysis

The issue occurred because: 1. WhiteNoise configuration was incomplete - Missing STATICFILES_STORAGE setting 2. Django's static() URL patterns don't work in DEBUG=False - They only serve in development 3. No proper production static file serving - Django isn't designed to serve static files efficiently in production 4. Cloudflare tunnel misconfiguration - Initially pointing to Django port instead of nginx

Solution Implemented

Phase 1: WhiteNoise Configuration (Attempted but Failed)

# settings.py
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
WHITENOISE_USE_FINDERS = True

Issue: WhiteNoise middleware conflicts and version incompatibilities made it unreliable for this setup.

Phase 2: Nginx Reverse Proxy Solution (Final Working Solution)

1. Installed and Configured Nginx

sudo apt install nginx

2. Created Nginx Site Configuration

# /etc/nginx/sites-available/vibe-hub
server {
    listen 80;
    listen [::]:80;
    server_name labyricorn.com www.labyricorn.com 10.138.0.121 localhost;

    # Serve static files with caching
    location /static/ {
        alias /var/www/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header X-Content-Type-Options nosniff;
        access_log off;
    }

    # Proxy Django requests
    location / {
        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;
    }
}

3. Moved Static Files to Nginx-Accessible Directory

mkdir -p /var/www/static
cp -r /root/web/vibe-website-labyricorn/staticfiles/* /var/www/static/
chown -R www-data:www-data /var/www/static/

4. Secured Gunicorn Configuration

# /etc/systemd/system/vibe-hub.service
ExecStart=/opt/vibe-hub/vibe-hub-mvp/bin/gunicorn \
    --bind 127.0.0.1:8000 \
    # ... rest of config

Security: Gunicorn now only listens on localhost, preventing external access to port 8000.

5. Updated Django ALLOWED_HOSTS

# settings.py
ALLOWED_HOSTS = ['labyricorn.com', 'www.labyricorn.com', '10.138.0.121', 'localhost', '127.0.0.1']

6. Fixed Cloudflare Tunnel Configuration

On tunnel server: Updated cloudflared config to point to port 80 instead of 8000:

ingress:
  - hostname: www.labyricorn.com
    service: http://10.138.0.121:80  # Changed from :8000

Architecture Overview

User → Cloudflare SSL → Cloudflare Tunnel → Nginx (Port 80)
                                      ↓
                           Static Files: Served directly by Nginx
                                      ↓
                           Django Requests: Proxied to Gunicorn (127.0.0.1:8000)

Performance Benefits

  • Static files cached for 1 year with proper HTTP headers
  • Reduced Django load - Only handles dynamic requests
  • Better security - Django not exposed externally
  • Optimal performance - Nginx excels at serving static files

Source Code Solutions for Future Projects

Option 1: WhiteNoise (Recommended for Simple Deployments)

# settings.py
INSTALLED_APPS = [
    'whitenoise.runserver_nostatic',  # Use whitenoise runserver in development
    # ... other apps
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # Must be after SecurityMiddleware
    # ... other middleware
]

STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles'

# WhiteNoise configuration
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
WHITENOISE_USE_FINDERS = True
WHITENOISE_AUTOREFRESH = True  # Development only

Option 2: Nginx + Gunicorn (Recommended for Production)

# deployment/setup_nginx.sh
#!/bin/bash
# Install nginx
sudo apt update && sudo apt install nginx

# Create directories
sudo mkdir -p /var/www/static
sudo mkdir -p /var/www/media
sudo chown -R www-data:www-data /var/www/

# Copy nginx config
sudo cp deployment/nginx.conf /etc/nginx/sites-available/app
sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default

# Test and reload
sudo nginx -t && sudo systemctl reload nginx
# deployment/nginx.conf
server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    # Static files
    location /static/ {
        alias /var/www/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Media files
    location /media/ {
        alias /var/www/media/;
        expires 30d;
        add_header Cache-Control "public";
    }

    # Django app
    location / {
        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;
    }
}

Option 3: Docker with Nginx

# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - static:/app/staticfiles

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - static:/var/www/static
    depends_on:
      - web

volumes:
  static:

Lessons Learned

  1. Don't rely on Django's static() in production - It's not designed for it
  2. WhiteNoise works but can be finicky - Nginx is more reliable for complex setups
  3. Always separate static file serving - Use a proper web server for static assets
  4. Secure your application server - Bind to localhost, use reverse proxy
  5. Test tunnel configurations - Ensure they point to the correct ports

Prevention Checklist for Future Projects

  • [ ] Use nginx or similar web server for static files in production
  • [ ] Configure reverse proxy to protect application server
  • [ ] Set proper cache headers for static assets
  • [ ] Test static file serving during deployment
  • [ ] Document infrastructure requirements clearly
  • [ ] Include nginx configuration in deployment scripts

>> From the build log

< RETURN_TO_BLOG