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:8080This 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:alpineNow 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.toolHTML 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>
EOFVisit 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:alpineDocker 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:roStart with:
docker-compose up -dServing 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:alpineNow 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 documentationSingle Page Application
app/
├── index.html # Main application
├── js/
│ ├── app.js # Application code
│ └── data.js # Data loading
├── css/
│ └── style.css # Styling
└── data/
└── papers.json # Static dataDevelopment 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-alpineTesting 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
waitSecurity 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 configCommon 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:alpineURL 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