Skip to main content

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.
PortServiceAccessible from
6379Redis127.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:
PortServiceAccessible from
443Nginx → dashboardInternet (your team)
80Nginx HTTP redirectInternet (redirects to 443)
3003Next.js dashboard127.0.0.1 only (Nginx proxies to it)
8000FastAPI127.0.0.1 only (Next.js proxy + internal tools)
5432PostgreSQL127.0.0.1 only
6379Redis (optional)127.0.0.1 only
8123ClickHouse HTTP127.0.0.1 only
9000ClickHouse native127.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:
EndpointWhat it checksUse for
GET /api/livenessProcess is aliveContainer restart policy
GET /api/readinessStorage reachableLoad 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