Problems Deploying Site to Remote Container
>> ubuntu, nginx, whitenoise
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
- Don't rely on Django's static() in production - It's not designed for it
- WhiteNoise works but can be finicky - Nginx is more reliable for complex setups
- Always separate static file serving - Use a proper web server for static assets
- Secure your application server - Bind to localhost, use reverse proxy
- 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