The Production Deployment Blueprint
Moving an asynchronous FastAPI + React application from a local development environment to an enterprise production server introduces infrastructure demands that code-level optimizations alone cannot solve:
- WebSocket Proxy Timeouts: Default Nginx reverse proxies drop idle TCP connections after 60 seconds. In an examination hall, this silently severs teacher proctoring screens mid-exam.
- Resource Isolation on Modest Hardware: Educational institutions frequently operate on cost-effective virtual private servers (e.g., 2 vCPU / 4GB RAM or 4 vCPU / 8GB RAM). The stack must comfortably orchestrate FastAPI, React, PostgreSQL 16, and Redis 7 without kernel OOM killer panics.
- Automated Self-Healing & Alerting: If an unexpected memory spike or database deadlock knocks the backend container offline at 08:00 AM, the system must self-heal automatically within seconds while notifying the engineering team via webhook.
In mfarim/fastapi-elearning-react, we engineered a resilient, single-VPS production architecture.
1. Multi-Container Docker Compose Orchestration
In docker-compose.yml, every service is defined with health checks, restart policies, and persistent storage volumes:
YAML
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
container_name: fastapi-elearning-postgres
restart: always
environment:
POSTGRES_DB: ${POSTGRES_DB:-elearning}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
ports:
- "127.0.0.1:5434:5432" # Bind only to localhost for security
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
networks:
- elearning-network
redis:
image: redis:7-alpine
container_name: fastapi-elearning-redis
restart: always
ports:
- "127.0.0.1:6380:6379" # Bind only to localhost
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
networks:
- elearning-network
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: fastapi-elearning-backend
restart: always
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-elearning}
REDIS_URL: redis://redis:6379/0
SECRET_KEY: ${SECRET_KEY}
ACCESS_TOKEN_EXPIRE_MINUTES: 120
ports:
- "127.0.0.1:8000:8000"
volumes:
- ./backend/storage:/app/storage
networks:
- elearning-network
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: fastapi-elearning-frontend
restart: always
ports:
- "127.0.0.1:3001:80"
networks:
- elearning-network
volumes:
postgres_data:
redis_data:
networks:
elearning-network:
driver: bridge
Key Security Practices:
- Localhost Port Binding (
127.0.0.1:5434): Database and Redis ports are bound strictly to loopback interfaces, preventing external scanners from accessing the database directly from the public internet. depends_onwithservice_healthy: The backend container waits until PostgreSQL passes its internalpg_isreadyprobe before booting, eliminating initialization race condition crashes.
2. Production Dockerfile: Lean & Non-Root Python 3.12
In backend/Dockerfile, we construct a clean, multi-layer image based on python:3.12-slim:
DOCKERFILE
# backend/Dockerfile
FROM python:3.12-slim
WORKDIR /app
# Install system dependencies required for asyncpg compilation & health checking
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
curl \
&& rm -rf /var/lib/apt/lists/*
# Cache Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# Copy application source code
COPY . .
# Ensure storage directories exist
RUN mkdir -p /app/storage/uploads
EXPOSE 8000
# Launch Uvicorn with optimized worker processes
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
3. Host Nginx Reverse Proxy with WebSocket Upgrades & SSL
On the host VPS, Nginx acts as the public-facing edge gateway. The configuration below provides SSL termination, security headers, and most importantly, WebSocket connection persistence:
NGINX
# /etc/nginx/sites-available/elearning.conf
# 1. HTTP to HTTPS redirection
server {
listen 80;
server_name elearning.sekolah.id;
return 301 https://$host$request_uri;
}
# 2. Production HTTPS Server
server {
listen 443 ssl http2;
server_name elearning.sekolah.id;
# SSL Certificates managed by Certbot
ssl_certificate /etc/letsencrypt/live/elearning.sekolah.id/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/elearning.sekolah.id/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Client upload limits for Excel & Assignment PDFs
client_max_body_size 30M;
# Hardened 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 Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# --- REST API Proxy ---
location /api/ {
proxy_pass http://127.0.0.1:8000/api/;
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;
}
# --- Interactive API Documentation ---
location /docs {
proxy_pass http://127.0.0.1:8000/docs;
proxy_set_header Host $host;
}
location /openapi.json {
proxy_pass http://127.0.0.1:8000/openapi.json;
proxy_set_header Host $host;
}
# --- WebSocket Proctoring Tunneling ---
location /ws/ {
proxy_pass http://127.0.0.1:8000/ws/;
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;
# CRITICAL: Prevent Nginx from terminating idle proctoring sockets after 60s
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
# --- Frontend SPA (React 19) ---
location / {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
The Critical WebSocket Directive: proxy_read_timeout 86400s
By default, Nginx enforces a 60-second read timeout (proxy_read_timeout 60s). If a candidate is thinking quietly about a difficult math question and no telemetry is sent over the WebSocket for 60 seconds, Nginx unilaterally severs the TCP connection.
Configuring proxy_read_timeout 86400s (24 hours) keeps the proctoring stream persistent for the entire duration of the school day.
4. Automated Health Monitoring & Auto-Recovery Script
Even the most meticulously designed applications can encounter unexpected outages. In scripts/health-monitor.sh, we implement an automated watchdog script:
BASH
#!/usr/bin/env bash
# ==============================================================================
# Production Health Monitor & Auto-Recovery Watchdog
# scripts/health-monitor.sh
# ==============================================================================
set -euo pipefail
TARGET_URL="${HEALTH_CHECK_URL:-http://localhost:8000/health}"
WEBHOOK_URL="${ALERT_WEBHOOK_URL:-https://discord.com/api/webhooks/your_webhook_here}"
TIMEOUT_SECONDS=5
PROJECT_DIR="/var/www/python-fastapi-react-elearning"
LOG_FILE="/var/log/elearning-health.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
send_alert() {
local message="$1"
if [ -n "$WEBHOOK_URL" ]; then
curl -s -X POST -H "Content-Type: application/json" \
-d "{\"content\": \"⚠️ **ALERT: E-Learning System Outage**\n$message\"}" \
"$WEBHOOK_URL" > /dev/null 2>&1 || true
fi
}
# 1. Probe the API health endpoint
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT_SECONDS" "$TARGET_URL" || echo "000")
if [ "$HTTP_STATUS" -eq 200 ]; then
log "HEALTH OK: API returned HTTP 200"
exit 0
else
log "HEALTH FAILURE: Endpoint returned HTTP $HTTP_STATUS! Initiating recovery..."
send_alert "FastAPI backend returned HTTP $HTTP_STATUS at $(hostname). Auto-restarting container..."
# 2. Execute automatic container restart
cd "$PROJECT_DIR"
docker compose restart backend
# 3. Wait and re-verify recovery
sleep 10
RETRY_STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT_SECONDS" "$TARGET_URL" || echo "000")
if [ "$RETRY_STATUS" -eq 200 ]; then
log "AUTO-RECOVERY SUCCESS: Backend restored to healthy status (HTTP 200)"
send_alert "✅ **RECOVERY SUCCESS**: FastAPI backend successfully restored at $(hostname)."
else
log "CRITICAL: Auto-recovery failed! Manual engineer intervention required."
send_alert "🚨 **CRITICAL ALERT**: Auto-recovery failed! Backend remains down (HTTP $RETRY_STATUS)."
fi
fi
Scheduling via Host Cron
Configure the watchdog to run every 5 minutes:
BASH
# Add to host crontab (crontab -e):
*/5 * * * * /var/www/python-fastapi-react-elearning/scripts/health-monitor.sh >> /var/log/elearning-cron.log 2>&1
If an outage occurs during off-hours, the watchdog automatically restarts the backend container within 5 minutes, restores normal service, and alerts the engineering team on Slack or Discord with the exact downtime window.
Complete Series Summary & Architectural Recap
Over the course of this 6-part series, we took a monolithic Laravel examination platform and transformed it into a cloud-ready, asynchronous microservice-capable architecture:
| Part | Topic | Architectural Highlight |
|---|---|---|
| Part 1 | Architecture & Monolith Migration | Synchronous PHP-FPM vs Asynchronous ASGI runtime, full stack comparison (Laravel vs Java vs Python). |
| Part 2 | Async ORM & Type Safety | SQLAlchemy 2.0 Async, selectinload N+1 prevention, and expire_on_commit=False greenlet safety. |
| Part 3 | High-Precision CBT Engine | Server-authoritative start times, 5-strike anti-cheat threshold, and native ASGI WebSockets with Redis Pub/Sub. |
| Part 4 | Enterprise Security Hardening | Stateless JWT tokens, declarative RBAC, 1-Click admin impersonation with zero-reauth session recovery. |
| Part 5 | Data Pipelines & Quality Assurance | Streaming openpyxl ingestion (<15MB RAM), atomic batch database transactions, and Pytest-Asyncio. |
| Part 6 | Single VPS Production Deployment | Multi-container Docker Compose, Nginx SSL WebSocket reverse proxying, and auto-recovery monitoring scripts. |
All source code, Docker configs, and automated test suites are fully open-source and available on GitHub at mfarim/fastapi-elearning-react.

