Overview
The standard docker compose up -d setup binds all services to localhost and is suitable for development. Before exposing LangSight to a team or the internet, apply the hardening steps on this page.
Horizontal scaling with Redis
LangSight supports multiple Uvicorn workers when Redis is configured.
Redis provides shared state for:
- Rate limiting — all workers share counters (brute-force protection works correctly)
- SSE live view — events published by any worker reach all connected clients
- Circuit breaker — shared failure state across all workers
- Alert deduplication — each alert fires exactly once, not once per worker
Quick setup
# 1. Generate password and configure
echo "REDIS_PASSWORD=$(openssl rand -hex 24)" >> .env
echo "LANGSIGHT_REDIS_URL=redis://:\${REDIS_PASSWORD}@redis:6379" >> .env
echo "LANGSIGHT_WORKERS=4" >> .env
# 2. Start with Redis profile
docker compose --profile redis up -d
For the full guide — including Sentinel HA, Cluster, monitoring, performance tuning, and troubleshooting — see the dedicated Redis page.
Without Redis (default)
LANGSIGHT_WORKERS must remain at 1 when LANGSIGHT_REDIS_URL is not set.
The API enforces this at startup — if you try to run LANGSIGHT_WORKERS=2 without Redis,
the process exits immediately with a clear error message.
This is the default behavior and requires no configuration for single-instance deployments.
Redis port security
Redis must never be internet-accessible. In the default Docker Compose setup,
Redis is bound to 127.0.0.1:6379 (loopback only) and inside the langsight-net network.
| Port | Service | Accessible from |
|---|
6379 | Redis | 127.0.0.1 only |
Nginx reverse proxy with TLS
Only the dashboard (port 3003 in Docker Compose) should be internet-accessible. The API (port 8000) should be internal only.
Full nginx.conf example
# /etc/nginx/sites-available/langsight
# Redirect HTTP → HTTPS
server {
listen 80;
server_name langsight.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name langsight.example.com;
# TLS certificates (use certbot or your own PKI)
ssl_certificate /etc/letsencrypt/live/langsight.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/langsight.example.com/privkey.pem;
# TLS hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Pass real client IP to the dashboard and API
# Required for per-IP rate limiting on the login endpoint
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
# Proxy to Next.js dashboard
location / {
proxy_pass http://127.0.0.1:3003;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Nginx should proxy only to port 3003 (Next.js). Do not add separate location /api/ or location /api/live/ blocks pointing to port 8000. All API calls — including the SSE live feed — go through the Next.js proxy at /api/live/stream, which injects authentication headers (X-User-Id, X-User-Role, X-API-Key) before forwarding to FastAPI. Routing SSE or API calls directly from Nginx to FastAPI bypasses this auth layer.
Enable and reload:
sudo ln -s /etc/nginx/sites-available/langsight /etc/nginx/sites-enabled/langsight
sudo nginx -t && sudo systemctl reload nginx
LANGSIGHT_TRUSTED_PROXY_CIDRS
When requests reach FastAPI through Nginx or any reverse proxy, FastAPI needs to know which proxy IPs are trusted before reading X-Forwarded-For for rate limiting. Set this to the CIDR of your proxy:
# Single proxy on the same host
LANGSIGHT_TRUSTED_PROXY_CIDRS=127.0.0.1/32,::1/128
# Docker Compose default bridge network (proxy container to API container)
LANGSIGHT_TRUSTED_PROXY_CIDRS=172.16.0.0/12
# Cloud load balancer (check your provider's documentation for the exact range)
LANGSIGHT_TRUSTED_PROXY_CIDRS=10.0.0.0/8
Without this setting, per-IP rate limiting on the login endpoint uses the proxy IP instead of the real client IP, which means all users share one rate-limit bucket.
LANGSIGHT_METRICS_TOKEN
The /metrics endpoint returns 503 when LANGSIGHT_METRICS_TOKEN is not set. Set the token before configuring Prometheus scraping:
# Generate a 32-byte hex token
python -c "import secrets; print(secrets.token_hex(32))"
# Add to your .env or docker-compose.yml environment
LANGSIGHT_METRICS_TOKEN=a3f8d2e1...
Prometheus scrape config:
scrape_configs:
- job_name: langsight
scrape_interval: 15s
bearer_token: a3f8d2e1...
static_configs:
- targets: ["localhost:8000"] # internal — do not expose 8000 externally
metrics_path: /metrics
Firewall rules
Only port 443 (HTTPS via Nginx) should be internet-accessible. All other ports must be restricted to your internal network:
| Port | Service | Accessible from |
|---|
443 | Nginx → dashboard | Internet (your team) |
80 | Nginx HTTP redirect | Internet (redirects to 443) |
3003 | Next.js dashboard | 127.0.0.1 only (Nginx proxies to it) |
8000 | FastAPI | 127.0.0.1 only (Next.js proxy + internal tools) |
5432 | PostgreSQL | 127.0.0.1 only |
6379 | Redis (optional) | 127.0.0.1 only |
8123 | ClickHouse HTTP | 127.0.0.1 only |
9000 | ClickHouse native | 127.0.0.1 only |
Example with ufw:
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Docker Compose binds all services to 127.0.0.1 by default. Do not add 0.0.0.0:8000:8000 port mappings for the API in production.
Basic health endpoint monitoring
Use /api/liveness and /api/readiness for external uptime monitoring. These endpoints require no authentication:
| Endpoint | What it checks | Use for |
|---|
GET /api/liveness | Process is alive | Container restart policy |
GET /api/readiness | Storage reachable | Load balancer routing |
Example with an uptime monitor (e.g. UptimeRobot, Grafana):
# Liveness — always returns 200 if the process is running
curl https://langsight.example.com/api/liveness
# Readiness — returns 503 if Postgres or ClickHouse is unreachable
curl https://langsight.example.com/api/readiness
For Docker Compose, add a healthcheck:
services:
api:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/readiness"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s