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 .envGenerate the three required secrets and add them to .env:
ENCRYPTION_KEYFernet 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_KEYSigns and verifies access tokens. Must be at least 32 characters.
python -c "import secrets; print(secrets.token_hex(32))"APP_DB_PASSWORDPassword 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_appOption 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 -dThis 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 psConfirm the backend started correctly (look for Embedding model loaded and Uvicorn running):
docker compose logs backend --tail 30Create the admin account
On a fresh deployment, the first person to open the browser is taken to theCreate 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 frontendAdd your domain to CORS_ORIGINS in .env, then restart the backend:
CORS_ORIGINS=["https://yourdomain.com"]docker compose restart backendFor 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 reloadLock 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.sqlIf 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 -dSet 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 llama3The 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 -dProduction 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=falseThis 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.
