Production Deployment
Overview
Section titled “Overview”The production instance of Chatty the Lab runs at https://tec.citius.usc.es/chatty. This guide covers the setup for running your own production deployment behind an nginx reverse proxy with HTTPS.
Reverse proxy (nginx)
Section titled “Reverse proxy (nginx)”The recommended setup routes all traffic through nginx, which handles TLS termination and proxies to the Docker services.
nginx configuration
Section titled “nginx configuration”server { listen 443 ssl http2; server_name tec.citius.usc.es;
ssl_certificate /etc/letsencrypt/live/tec.citius.usc.es/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/tec.citius.usc.es/privkey.pem;
# Frontend (SvelteKit) location /chatty { proxy_pass http://127.0.0.1:3000/chatty; 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; }
# Backend API location /chatty/api { proxy_pass http://127.0.0.1:5000/chatty/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; }
# WebSocket location /chatty/ws { proxy_pass http://127.0.0.1:5000/chatty/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_read_timeout 86400; }
# Supabase Auth (if exposed) location /chatty/auth { proxy_pass http://127.0.0.1:54321; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
# Documentation (Starlight static site) location /chatty/docs { alias /path/to/docs/dist/; try_files $uri $uri/ =404; }}
server { listen 80; server_name tec.citius.usc.es; return 301 https://$server_name$request_uri;}Base path configuration
Section titled “Base path configuration”The base path /chatty is configurable and must be consistent across all components:
| Component | Variable | Value |
|---|---|---|
| Backend | BASE_PATH | /chatty |
| Frontend | PUBLIC_BASE_PATH | /chatty |
| SvelteKit | base in svelte.config.js | /chatty |
| Nginx | location prefix | /chatty |
HTTPS with Let’s Encrypt
Section titled “HTTPS with Let’s Encrypt”Use certbot to obtain and auto-renew certificates:
# Install certbotsudo apt install certbot python3-certbot-nginx
# Obtain certificatesudo certbot --nginx -d tec.citius.usc.es
# Auto-renewal is configured automatically# Verify with:sudo certbot renew --dry-runEnvironment variables for production
Section titled “Environment variables for production”Create a .env file alongside the production compose file:
# SupabaseSUPABASE_JWT_SECRET=generate-a-strong-random-secretPOSTGRES_PASSWORD=generate-a-strong-passwordPUBLIC_SUPABASE_ANON_KEY=your-anon-key
# Google OAuthGOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.comGOOGLE_CLIENT_SECRET=your-client-secret
# URLsSITE_URL=https://tec.citius.usc.es/chattySUPABASE_URL=http://supabase-auth:9999
# API keysOPENAI_KEY=sk-...MISTRAL_KEY=...GEMINI_KEY=...Supabase production setup
Section titled “Supabase production setup”When running Supabase in production (via docker-compose.prod.yml), you manage your own PostgreSQL and GoTrue instances instead of using the Supabase CLI.
Database initialization
Section titled “Database initialization”Apply migrations manually after first deploying supabase-db:
# Copy migrations into the container and applyfor f in supabase/migrations/*.sql; do docker compose -f docker-compose.prod.yml exec -T supabase-db \ psql -U postgres -d postgres < "$f"doneGenerating a JWT secret
Section titled “Generating a JWT secret”openssl rand -base64 32Use this value for both SUPABASE_JWT_SECRET and the GoTrue GOTRUE_JWT_SECRET.
Generating an anon key
Section titled “Generating an anon key”The anon key is a JWT signed with the secret that has the role: anon claim:
node -e "const jwt = require('jsonwebtoken');console.log(jwt.sign( { role: 'anon', iss: 'supabase', iat: Math.floor(Date.now()/1000), exp: Math.floor(Date.now()/1000) + 10*365*24*3600 }, 'YOUR_JWT_SECRET'));"Scaling considerations
Section titled “Scaling considerations”Backend
Section titled “Backend”The Rust backend is single-binary and handles concurrency via tokio’s async runtime. A single instance can handle hundreds of concurrent WebSocket connections. For higher loads:
- Increase the SQLx connection pool size
- Run multiple backend instances behind a load balancer (WebSocket connections are stateless after auth)
Ollama
Section titled “Ollama”Ollama is typically the bottleneck for local models. Options for scaling:
- Use GPU acceleration (configured in
docker-compose.prod.yml) - Run multiple Ollama instances on different GPUs
- Offload to cloud providers (OpenAI, Mistral) for high-traffic models
Database
Section titled “Database”Supabase PostgreSQL handles concurrent reads well. For write-heavy workloads:
- Monitor connection pool usage
- Consider connection pooling with PgBouncer
- Optimize pgvector IVFFlat indexes as the
context_itemstable grows
Monitoring
Section titled “Monitoring”Recommended monitoring setup:
- Backend logs: Uses
tracingwith configurable log levels viaRUST_LOG - Database: Monitor with
pg_stat_statementsand connection pool metrics - Nginx: Standard access/error logs at
/var/log/nginx/
# Set backend log levelRUST_LOG=info,chatty_backend=debug docker compose -f docker-compose.prod.yml up backend