Serving Static Content with nginx
Web Servers vs Application Code
When you visit a website, the server returns different types of content:
Static Content - Files that never change:
- HTML pages:
index.html - CSS stylesheets:
styles.css - JavaScript files:
app.js - Images:
logo.png,photo.jpg - Documents:
report.pdf,data.json
Dynamic Content - Generated by running code:
- User profiles (different for each user)
- Search results (different for each query)
- Current weather data
- Database query results
Web servers like nginx are optimized for serving static files directly from disk. Application servers run your Python/Java/etc. code to generate dynamic content.
For static files, nginx is:
- Faster - No code execution, just file system reads
- More efficient - Uses less memory and CPU
- More reliable - Fewer moving parts to break
What is nginx?
nginx (pronounced “engine-x”) is a web server that excels at serving static files and acting as a reverse proxy. It handles thousands of concurrent connections efficiently.
Common nginx uses:
- Serve static websites (HTML, CSS, images)
- Serve API documentation and data files
- Load balance between application servers
- Handle SSL/HTTPS certificates
- Cache frequently requested content
For your assignments, nginx provides an easy way to serve JSON data files or HTML reports over HTTP.
MIME Types
Web browsers need to know what type of file they’re receiving. The server tells them using MIME types in HTTP headers:
Content-Type: text/html # HTML pages
Content-Type: text/css # CSS stylesheets
Content-Type: application/json # JSON data
Content-Type: image/png # PNG images
Content-Type: application/pdf # PDF documents
nginx automatically sets the correct MIME type based on file extensions:
.html→text/html.json→application/json.css→text/css.js→application/javascript.png→image/png.pdf→application/pdf
This is why file extensions matter for web content.
Basic nginx Container
The simplest way to serve files with nginx:
# Create some content to serve mkdir website echo '<h1>Hello World</h1>' > website/index.html echo '{"message": "Hello from JSON"}' > website/data.json # Serve with nginx container docker run --rm \ --name static-server \ -p 8080:80 \ -v $(pwd)/website:/usr/share/nginx/html:ro \ nginx:alpine # Access at http://localhost:8080
This mounts your website/ directory to nginx’s default document root (/usr/share/nginx/html).
Directory Structure
nginx serves files based on URL paths:
website/
├── index.html # http://localhost:8080/
├── about.html # http://localhost:8080/about.html
├── data.json # http://localhost:8080/data.json
├── css/
│ └── style.css # http://localhost:8080/css/style.css
├── images/
│ └── logo.png # http://localhost:8080/images/logo.png
└── api/
└── papers.json # http://localhost:8080/api/papers.json
URL path maps directly to file system path:
/→index.html(default)/about.html→about.html/api/papers.json→api/papers.json
Serving JSON Data
Perfect for serving API data files or results from data processing:
# Create API data structure mkdir -p api-data/api/v1 mkdir -p api-data/docs # Sample JSON files cat > api-data/api/v1/papers.json << 'EOF' [ { "id": "2301.12345", "title": "Deep Learning Paper", "authors": ["Smith", "Jones"], "abstract": "This paper discusses..." } ] EOF cat > api-data/api/v1/stats.json << 'EOF' { "total_papers": 150, "categories": { "cs.LG": 45, "cs.AI": 32 } } EOF # Serve the data docker run --rm \ --name json-server \ -p 8080:80 \ -v $(pwd)/api-data:/usr/share/nginx/html:ro \ nginx:alpine
Now you can access:
http://localhost:8080/api/v1/papers.jsonhttp://localhost:8080/api/v1/stats.json
Test with curl:
curl http://localhost:8080/api/v1/papers.json | python -m json.tool
HTML Interface for Data
Create a simple web interface to view your data:
# Create HTML page that loads JSON via JavaScript cat > api-data/index.html << 'EOF' <!DOCTYPE html> <html> <head> <title>Papers API</title> <style> body { font-family: Arial, sans-serif; margin: 40px; } .paper { border: 1px solid #ddd; padding: 10px; margin: 10px 0; } pre { background: #f5f5f5; padding: 10px; overflow-x: auto; } </style> </head> <body> <h1>Papers Data</h1> <div id="papers"></div> <h2>Stats</h2> <div id="stats"></div> <script> // Load papers fetch('/api/v1/papers.json') .then(response => response.json()) .then(papers => { const container = document.getElementById('papers'); papers.forEach(paper => { container.innerHTML += ` <div class="paper"> <h3>${paper.title}</h3> <p>Authors: ${paper.authors.join(', ')}</p> <p>${paper.abstract.substring(0, 200)}...</p> </div> `; }); }); // Load stats fetch('/api/v1/stats.json') .then(response => response.json()) .then(stats => { document.getElementById('stats').innerHTML = '<pre>' + JSON.stringify(stats, null, 2) + '</pre>'; }); </script> </body> </html> EOF
Visit http://localhost:8080/ to see your data in a web interface.
Configuration File
For more control, create custom nginx configuration:
# Create nginx config cat > nginx.conf << 'EOF' server { listen 80; server_name localhost; # Document root root /usr/share/nginx/html; # Default file index index.html; # Enable gzip compression gzip on; gzip_types application/json text/css text/javascript; # CORS headers for API endpoints location /api/ { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods "GET, OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type"; } # Cache static assets location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { expires 1h; add_header Cache-Control "public, immutable"; } # Security headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; } EOF # Run with custom config docker run --rm \ --name custom-server \ -p 8080:80 \ -v $(pwd)/api-data:/usr/share/nginx/html:ro \ -v $(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf:ro \ nginx:alpine
Docker Compose Setup
For more complex setups, use docker-compose:
# docker-compose.yml version: '3.8' services: nginx: image: nginx:alpine ports: - "8080:80" volumes: - ./website:/usr/share/nginx/html:ro - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro restart: unless-stopped # Example: serve different content on different ports api-server: image: nginx:alpine ports: - "8081:80" volumes: - ./api-data:/usr/share/nginx/html:ro
Start with:
docker-compose up -d
Serving Assignment Results
Common pattern for homework assignments:
# Process data and generate results python process_arxiv.py data/papers.json output/ # Results directory structure output/ ├── index.html # Summary report ├── results.json # Raw data ├── analysis.json # Analysis results ├── charts/ │ ├── categories.png # Generated charts │ └── timeline.png └── css/ └── report.css # Styling # Serve results docker run --rm \ --name results-server \ -p 8080:80 \ -v $(pwd)/output:/usr/share/nginx/html:ro \ nginx:alpine
Now you can share results with: “Visit http://localhost:8080 to see the analysis”
Common Patterns
API-like Structure
data/ ├── index.html # Documentation ├── v1/ │ ├── papers.json │ ├── authors.json │ └── stats.json ├── v2/ │ └── papers.json # Updated format └── docs/ └── api.html # API documentation
Single Page Application
app/ ├── index.html # Main application ├── js/ │ ├── app.js # Application code │ └── data.js # Data loading ├── css/ │ └── style.css # Styling └── data/ └── papers.json # Static data
Development vs Production
# Development - auto-reload on file changes docker run --rm \ -p 8080:80 \ -v $(pwd)/src:/usr/share/nginx/html \ nginx:alpine # Production - read-only, specific tag docker run -d \ --name prod-server \ -p 80:80 \ -v /var/www/html:/usr/share/nginx/html:ro \ nginx:1.21-alpine
Testing Static Server
Verify your static server works correctly:
# Test basic connectivity curl -v http://localhost:8080/ # Test JSON endpoint curl -H "Accept: application/json" http://localhost:8080/api/data.json # Test MIME types curl -I http://localhost:8080/style.css # Should be text/css curl -I http://localhost:8080/data.json # Should be application/json # Test 404 handling curl -I http://localhost:8080/nonexistent.html # Should be 404 # Load test for i in {1..100}; do curl -s http://localhost:8080/ > /dev/null & done wait
Security Considerations
Basic security for static content:
server { listen 80; # Prevent access to hidden files location ~ /\. { deny all; return 404; } # Prevent access to sensitive files location ~* \.(env|log|backup|tmp)$ { deny all; return 404; } # Rate limiting location / { limit_req zone=basic burst=10; try_files $uri $uri/ =404; } }
Debugging nginx
Common issues and solutions:
# Check nginx is running docker ps | grep nginx # View nginx logs docker logs container_name # Test nginx config syntax docker run --rm \ -v $(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf:ro \ nginx:alpine nginx -t # Access container to debug docker exec -it container_name sh # Inside container: ls -la /usr/share/nginx/html/ # Check files are mounted cat /etc/nginx/conf.d/default.conf # Check config
Common errors:
- 403 Forbidden: Check file permissions and volume mounts
- 404 Not Found: Verify file paths and directory structure
- Connection refused: Check port mapping and firewall
Quick Reference
Basic nginx Container
# Serve current directory docker run --rm -p 8080:80 -v $(pwd):/usr/share/nginx/html:ro nginx:alpine # With custom config docker run --rm -p 8080:80 \ -v $(pwd)/content:/usr/share/nginx/html:ro \ -v $(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf:ro \ nginx:alpine
URL Mapping
URL File
/ → index.html
/about.html → about.html
/api/data.json → api/data.json
/css/style.css → css/style.css
Common MIME Types
.html → text/html
.css → text/css
.js → application/javascript
.json → application/json
.png → image/png
.jpg → image/jpeg
.pdf → application/pdf
.txt → text/plain
Docker Commands
docker run --rm -p 8080:80 -v $(pwd):/usr/share/nginx/html:ro nginx:alpine docker logs nginx-container docker exec -it nginx-container sh