Deployment
Simmetric Chat è air-gap capable e local-first. Cinque target di deployment: Docker Compose multi-container (raccomandata per produzione), container singolo all-in-one (ambienti air-gapped), stack gestito Coolify, override dev (hot-reload su Docker) e de…
Simmetric Chat è air-gap capable e local-first. Cinque target di deployment: Docker Compose multi-container (raccomandata per produzione), container singolo all-in-one (ambienti air-gapped), stack gestito Coolify, override dev (hot-reload su Docker) e desktop Tauri. Tutti gli artefatti vivono in docker/; la shell desktop in src-tauri/. Le immagini pubblicate su GHCR sono solo amd64 (linux/amd64 - le build arm64 emulate via QEMU si bloccavano; su arm64 si fa self-build locale dall'host nativo). Tutti i container usano healthcheck wget (--spider): server GET /api/health, collector GET /api/health su 3210, widget GET /health su 3211, Postgres pg_isready, Redis redis-cli ping.
Prerequisiti
- Node.js ≥ 24, pnpm 12.8.1 (pinnato), PostgreSQL 16.
- Per embedding locale: ~2 GB RAM per
all-MiniLM-L6-v2(Xenova, CPU). - Per LLM locale: Ollama con modello(s) pre-pullati (
gemma4:latestdefault). - Per OCR vision: Ollama con modello vision (
glm-ocr:latestdefault).
Docker Compose (produzione)
Il compose di produzione (docker/docker-compose.yml) definisce 7 servizi attivi: frontend (nginx, :80/:443), server (:3000), collector (:3210), widget (:3211), postgres (16, immagine pgvector/pgvector:pg16), redis (7-alpine, cache di scaling opzionale) e `ollama` attivo di default (decisione owner 2026-09-08 - il provider «fetch failed» e l'Ollama Cloud login richiedono un container ollama reale sulla rete compose; volume modelli proprio ollama-data, i modelli si pullano via app UI o docker exec). Il servizio `llmster` (LM Studio, lmstudio/llmster-preview:cpu) esiste ma è profile-gated (profiles: [lmstudio]). Il servizio qdrant non esiste più nel compose (vettoriale = LanceDB di default; VECTOR_DB_PROVIDER=qdrant resta provider remoto via codice).
cp .env.example .env
# Modifica .env: JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, DATABASE_URL, LICENSE_KEY (se Enterprise)
docker compose -f docker/docker-compose.yml up -dEnv critici di produzione (vedi 03 - Configuration):
JWT_SECRET- obbligatorio, materiale critografico (derivaENCRYPTION_KEYse unset).COLLECTOR_SECRET- obbligatorio, deve matchare tra server e collector.WIDGET_API_KEY- obbligatorio per widget, deve matchare tra widget e server.DATABASE_URL- PostgreSQL 16.LICENSE_KEY- solo Enterprise (JWT RS256; la chiave pubblica è embedded nel sorgente; alternativa no-.env = la paste in admin UI /PUT /api/license/instance).ENCRYPTION_KEY(base64 32 byte) - consigliato per data-at-rest persistente oltre rotazioni diJWT_SECRET.
Rotazione chiavi: imposta LEGACY_PREVIOUS_ENCRYPTION_KEYS (CSV base64 32-byte)per mantenere decryptabilità dei backup/dati vecchi durante la rotazione. Non esiste
job automatico di re-encryption: procedura manuale decrypt-old → re-encrypt-new.
Auto-provisioning chiavi (entrypoint server)
L'entrypoint di produzione (docker/entrypoint-server.sh) esegue a ogni boot: provisioning di ENCRYPTION_KEY e API_KEY_HMAC_SECRET con precedenza valore operator-supplied > valore persistito nel volume server-storage > generato una volta e persistito (mai rigenerato su materiale esistente); poi prisma migrate deploy + seed e l'avvio del server. Le configurazioni dev (docker-compose.dev.yml) saltano il provisioning e mantengono il fallback scrypt.
Volumi & persistenza
- PostgreSQL: volume dedicato (
pgdata). - LanceDB:
${STORAGE_PATH}/vectors/lancedb(collector). - File storage:
${STORAGE_PATH}(collector, documenti/avatar/uploads). - Backup: destinazioni configurate per-job (disk/S3/Dropbox/...).
Health check post-deploy
I container si auto-verificano via wget (vedi intro). Check manuali equivalenti:
curl http://localhost:3000/api/health
# { "status": "ok", "checks": { "database": "ok", "collector": "ok", "disk": "ok" } }
curl http://localhost:3210/api/health # collector
curl http://localhost:3211/health # widgetContainer singolo all-in-one
docker/Dockerfile costruisce un'unica immagine supervisionata da supervisord che esegue PostgreSQL 16 (pgvector compilato offline per air-gap), server, collector e widget. Espone 3000/3210/3211, usa DATABASE_URL su localhost, healthcheck su /api/health. Pensata per ambienti air-gapped/offline/demo: gli env di default sono placeholder - sovrascrivili via .env o -e.
Coolify
docker/docker-compose.coolify.yml + docker/nginx.coolify.conf automatizzano build delle immagini, networking interno, volumi, healthcheck e il mount del plugin enterprise (path host assoluto ${ENTERPRISE_PLUGIN_PATH:-/opt/simmetric-enterprise}). I segreti richiesti usano la sintassi :? (Coolify rifiuta il deploy se mancano): JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, POSTGRES_PASSWORD, APP_URL, ALLOWED_ORIGINS. Differenze dal compose base: nessun container_name, nessuna porta host sulle app (ingress affidato a Traefik, HTTP-only - TLS terminato da Traefik), ENCRYPTION_KEY/API_KEY_HMAC_SECRET non interpolate (auto-provisionati nel volume).
Sviluppo locale
pnpm install
pnpm db:generate # Prisma client (+ fix symlink Prisma/pnpm)
pnpm --filter server db:migrate # applica le migrazioni
pnpm dev # turborepo: server + collector + widget + frontend paralleliDettagli in 10 - Development.
Override dev (Docker)
docker/docker-compose.dev.yml monta i sorgenti sul compose di produzione per hot-reload (NODE_ENV=development). docker-compose.infra.yml avvia solo Postgres/Qdrant con l'app in esecuzione sull'host.
Desktop (Tauri)
src-tauri/ - app desktop che wrappa il frontend. Build con pnpm tauri build. Configura src-tauri/tauri.conf.json per URL server remoto o bundle locale.
Reverse proxy (Nginx)
Punti chiave:
- SSE: disabilitare buffering (
proxy_buffering off;,X-Accel-Buffering: nogià inviato dal server/widget). - Timeout: LLM locali lenti →
proxy_read_timeoutalto (≥ 600s, l'agent watchdog default è 10 min). - Upload:
client_max_body_size≥ 100 MB (limite multer documenti). - CORS: l'allowlist è gestita lato app (
ALLOWED_ORIGINS); il widget ha CORS dinamico per-origin (allowedOrigins).
Backup in produzione
- Crea destinazione (
POST /api/backup-destinations). - Crea job (
POST /api/backup-jobs) con cronfrequency/time+retentionDays. - Verifica con
POST /backup-destinations/:id/teste un run manualePOST /backup-jobs/:id/run. - Restore:
POST /api/backups/restore/:logId(dry-run consigliato prima:/restore/:logId/dry-run). Atomicità via psql--single-transaction.
Vedi 05 - Feature Guide § Backup.
Sicurezza di produzione
- Cambia password admin bootstrap; imposta
ALLOW_REGISTRATION=falsein prod (self-registration chiusa). DLP_ENABLEDdefault"true"in v0.22 - redaction input/output chat su 10 pattern built-in (vedi 05 - Feature Guide); usaDLP_BYPASS_ROLESper esentare ruoli selezionati.- Abilita
audit_log_immutable(Enterprise) per audit append-only. - HTTPS terminato al reverse proxy; header
helmetattivi sul server (rilassati sul widget per embedding). - Rate limit già configurati (vedi 04 - API Reference § Rate limit): il limite chat è ora gestito dal budget tracker dell'agent (concorrenza per-utente, wallclock, token) più la rete di protezione globale 200 req/min.
Provenienza v0.28.1
| Affermazione | Fonte |
|---|---|
| 7 servizi attivi + ollama on di default + llmster profile-gated + qdrant assente | docker/docker-compose.yml (row-time probe 35-02) |
| pnpm 12.8.1 | docs/DEVELOPMENT.md (Prerequisites, row-time) |
Riferimento operativo - Simmetric Chat v0.28.1 (docs aggiornate 2026-10-02, census SHA `aca5b8ff`).
Cross-link
- Env completi: 03 - Configuration
- Sviluppo: 10 - Development
- Troubleshooting deploy: 12 - Troubleshooting