Self-hosted in under 15 minutes
Community EditionSavvina AI Community Edition ships as a Docker Compose stack. One command starts all services. This guide covers a production deployment on a Linux server with HTTPS.
Prerequisites
- Linux server (Ubuntu 22.04+ or RHEL 9+ recommended)
- Docker Engine 24+ with the Compose plugin (docker compose, not docker-compose)
- At least 4 GB RAM — 8 GB+ recommended under concurrent load
- At least 30 GB disk — the backend image is ~11 GB (sentence-transformer model baked in); budget additional space for the PostgreSQL data volume, build cache, and OS
- A domain name with DNS pointing to the server (for HTTPS)
- At least one LLM API key (Groq and Google Gemini have free tiers)
Prepare the environment
Clone the repository, copy the example env file, and generate required secrets.
git clone https://github.com/savvina-ai/savvina
cd savvina
cp .env.example .env
chmod 600 .env Generate the three required secrets and add them to .env:
ENCRYPTION_KEY Fernet symmetric key for encrypting stored credentials. Generate once and never change it in production.
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" JWT_SECRET_KEY Signs and verifies access tokens. Must be at least 32 characters.
python -c "import secrets; print(secrets.token_hex(32))" APP_DB_PASSWORD Password for the internal PostgreSQL app database (local-db profile only).
python -c "import secrets; print(secrets.token_urlsafe(24))" ENCRYPTION_KEY: Generate once and never change it in production. Rotating the key requires re-encrypting every stored credential. Use a unique key per environment — never reuse a development key in production.
Choose a database mode
Option A — Local Docker PostgreSQL (default)
Set COMPOSE_PROFILES=local-db in .env. The DATABASE_URL must use the Docker service name as host:
COMPOSE_PROFILES=local-db
APP_DB_PASSWORD=<strong-password>
DATABASE_URL=postgresql+asyncpg://savvina:<strong-password>@db:5432/savvina_app Option B — External / Managed PostgreSQL
For AWS RDS, GCP Cloud SQL, Supabase, Neon, Azure, etc.: leave COMPOSE_PROFILES unset (the bundled db container will not start) and point DATABASE_URL at your provider:
DATABASE_URL=postgresql+asyncpg://USER:PASSWORD@your-db-host.example.com:5432/savvina_app
# Append ?ssl=require if your provider enforces SSL (most do) The database user needs CREATE TABLE, ALTER TABLE, and CREATE INDEX privileges. Migrations run automatically on startup.
Build and start
docker compose up --build -d This starts the core services: backend (FastAPI on port 8000), frontend (React/Nginx on port 3000), and db (PostgreSQL, local-db profile only).
Verify all services are healthy:
docker compose ps Confirm the backend started correctly (look for Embedding model loaded and Uvicorn running):
docker compose logs backend --tail 30 Create the admin account
On a fresh deployment, the first person to open the browser is taken to the Create Admin Account screen. Enter an email and a password (minimum 12 characters). This screen appears only once — the registration endpoint closes permanently as soon as any account exists.
Configure HTTPS
TLS is terminated by the Nginx server inside the frontend container.
Obtain a certificate from Let's Encrypt (recommended) or your CA, then install it:
# Obtain (Let's Encrypt)
apt install certbot -y
certbot certonly --standalone -d yourdomain.com
# Install
cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem volumes/certs/
cp /etc/letsencrypt/live/yourdomain.com/privkey.pem volumes/certs/
# Rebuild frontend once to pick up nginx.conf changes
docker compose build frontend && docker compose up -d frontend
Add your domain to CORS_ORIGINS in .env, then restart the backend:
CORS_ORIGINS=["https://yourdomain.com"] docker compose restart backend For subsequent cert renewals, no rebuild is needed — copy the new files and reload Nginx in place:
cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem volumes/certs/
cp /etc/letsencrypt/live/yourdomain.com/privkey.pem volumes/certs/
docker compose exec frontend nginx -s reload Lock down exposed ports
By default, docker-compose.yaml publishes port 3000 (frontend) and port 8000 (backend API). Do not expose port 8000 to the internet. Bind both services to localhost only via an override file:
# docker-compose.override.yaml
services:
backend:
ports:
- "127.0.0.1:8000:8000"
frontend:
ports:
- "127.0.0.1:3000:80" Backups
All application state — user accounts, saved connections (with encrypted credentials),
chat history, query cache — lives in volumes/app-db/ (local-db profile).
Back it up regularly with pg_dump:
docker compose exec db pg_dump -U savvina savvina_app > backup.sql If using an external/managed PostgreSQL, backups are your provider's responsibility — enable automated snapshots and PITR in your provider's console.
Also back up your ENCRYPTION_KEY separately.
Without it, stored connection credentials cannot be decrypted.
Sample databases (optional)
The test-dbs profile starts two pre-seeded databases so you can evaluate Savvina AI without connecting to a real data source:
| Service | Engine | Port | Database |
|---|---|---|---|
sample-postgres | PostgreSQL 16 | 5435 | savvina_test |
sample-mysql | MySQL 8.0 | 3307 | sample_delivery |
# With local Docker DB:
docker compose --profile local-db --profile test-dbs up -d
# With external/managed PostgreSQL:
docker compose --profile test-dbs up -d Set the required passwords in .env before starting:
SAMPLE_POSTGRES_PASSWORD=<strong-password>
SAMPLE_MYSQL_ROOT_PASSWORD=<strong-password>
SAMPLE_MYSQL_PASSWORD=<strong-password> Local LLM with Ollama (optional)
Run a local Ollama instance if you want fully offline inference. A GPU is required for acceptable performance.
docker compose --profile local-llm up -d
docker compose exec ollama ollama pull llama3
The container is reachable from the backend at http://ollama:11434 inside the Docker network. Configure it via Settings → Providers → Add Provider → Ollama.
Updating
Pull the latest changes and rebuild. Database schema migrations apply automatically
on startup via alembic upgrade head — no manual migration step required. The volumes/ directory is never touched during updates.
git pull
docker compose build backend frontend
docker compose up -d Production checklist
| Variable | Production value |
|---|---|
ENCRYPTION_KEY | Unique Fernet key — never reuse across environments |
JWT_SECRET_KEY | At least 32 random characters |
LOG_LEVEL | WARNING or INFO (not DEBUG) |
DEBUG | false |
DEFAULT_ROW_LIMIT | 500 or 1000 |
DEFAULT_QUERY_TIMEOUT | 30 (seconds) |
VERIFY_SSL | true (set false only behind a TLS-inspecting proxy) |
Health check
The backend exposes a health endpoint used by Docker Compose and your load balancer:
curl http://localhost:8000/health
# {"status": "ok", "app": "Savvina AI"} Corporate TLS proxies
If your network uses a TLS-intercepting proxy (Zscaler, Netskope, Palo Alto),
LLM API calls may fail with certificate errors. Set in .env:
VERIFY_SSL=false This affects only LLM provider HTTP clients — not the asyncpg database TLS stack.
Troubleshooting
Backend won't start
docker compose logs backend - ENCRYPTION_KEY missing or malformed (must be a valid Fernet key)
- Port 8000 already in use on the host
Embedding model download fails
- The model (BAAI/bge-small-en-v1.5) is fetched from HuggingFace Hub on first startup. If the server has no internet access, pre-bake it into the Docker image — see the README for the Dockerfile snippet.
LLM calls fail with SSL errors
- Set VERIFY_SSL=false in .env (see Corporate TLS section above).
Database connection errors (local-db)
docker compose ps db
docker compose logs db --tail 20 - APP_DB_PASSWORD not set in .env
- COMPOSE_PROFILES=local-db missing from .env — the container won't start without it
- DATABASE_URL still points to localhost — inside Docker the host must be db, not localhost
- Port 5434 already in use on the host
- Volume permission issue on Linux — set LOCAL_UID/LOCAL_GID in .env to match your host user
Database connection errors (external PostgreSQL)
docker compose logs backend --tail 30 - Wrong hostname, port, user, or password in DATABASE_URL
- Missing ?ssl=require — most managed providers enforce SSL
- Firewall or security group not allowing the Docker host's outbound IP
Deployment questions or issues? Email info@savvina.ai. For the full configuration reference see the configuration docs on GitHub.
