Torna alla documentazione
Guida 09

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…

5 min di lettura

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:latest default).
  • Per OCR vision: Ollama con modello vision (glm-ocr:latest default).

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

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

Env critici di produzione (vedi 03 - Configuration):

  • JWT_SECRET - obbligatorio, materiale critografico (deriva ENCRYPTION_KEY se 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 di JWT_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:

bash
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        # widget

Container 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

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

Dettagli 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: no già inviato dal server/widget).
  • Timeout: LLM locali lenti → proxy_read_timeout alto (≥ 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

  1. Crea destinazione (POST /api/backup-destinations).
  2. Crea job (POST /api/backup-jobs) con cron frequency/time + retentionDays.
  3. Verifica con POST /backup-destinations/:id/test e un run manuale POST /backup-jobs/:id/run.
  4. 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=false in prod (self-registration chiusa).
  • DLP_ENABLED default "true" in v0.22 - redaction input/output chat su 10 pattern built-in (vedi 05 - Feature Guide); usa DLP_BYPASS_ROLES per esentare ruoli selezionati.
  • Abilita audit_log_immutable (Enterprise) per audit append-only.
  • HTTPS terminato al reverse proxy; header helmet attivi 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

AffermazioneFonte
7 servizi attivi + ollama on di default + llmster profile-gated + qdrant assentedocker/docker-compose.yml (row-time probe 35-02)
pnpm 12.8.1docs/DEVELOPMENT.md (Prerequisites, row-time)

Riferimento operativo - Simmetric Chat v0.28.1 (docs aggiornate 2026-10-02, census SHA `aca5b8ff`).