Skip to content

Production Deployment

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.

The recommended setup routes all traffic through nginx, which handles TLS termination and proxies to the Docker services.

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;
}

The base path /chatty is configurable and must be consistent across all components:

ComponentVariableValue
BackendBASE_PATH/chatty
FrontendPUBLIC_BASE_PATH/chatty
SvelteKitbase in svelte.config.js/chatty
Nginxlocation prefix/chatty

Use certbot to obtain and auto-renew certificates:

Terminal window
# Install certbot
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d tec.citius.usc.es
# Auto-renewal is configured automatically
# Verify with:
sudo certbot renew --dry-run

Create a .env file alongside the production compose file:

Terminal window
# Supabase
SUPABASE_JWT_SECRET=generate-a-strong-random-secret
POSTGRES_PASSWORD=generate-a-strong-password
PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# Google OAuth
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
# URLs
SITE_URL=https://tec.citius.usc.es/chatty
SUPABASE_URL=http://supabase-auth:9999
# API keys
OPENAI_KEY=sk-...
MISTRAL_KEY=...
GEMINI_KEY=...

When running Supabase in production (via docker-compose.prod.yml), you manage your own PostgreSQL and GoTrue instances instead of using the Supabase CLI.

Apply migrations manually after first deploying supabase-db:

Terminal window
# Copy migrations into the container and apply
for f in supabase/migrations/*.sql; do
docker compose -f docker-compose.prod.yml exec -T supabase-db \
psql -U postgres -d postgres < "$f"
done
Terminal window
openssl rand -base64 32

Use this value for both SUPABASE_JWT_SECRET and the GoTrue GOTRUE_JWT_SECRET.

The anon key is a JWT signed with the secret that has the role: anon claim:

Terminal window
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'
));
"

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 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

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_items table grows

Recommended monitoring setup:

  • Backend logs: Uses tracing with configurable log levels via RUST_LOG
  • Database: Monitor with pg_stat_statements and connection pool metrics
  • Nginx: Standard access/error logs at /var/log/nginx/
Terminal window
# Set backend log level
RUST_LOG=info,chatty_backend=debug docker compose -f docker-compose.prod.yml up backend