Blog
Tutorial

Fare il deployment di Simmetric Chat con Docker: guida completa

La mappa completa del deployment di Simmetric Chat - uno stack Compose a sei servizi, un singolo contenitore all-in-one per host air-gapped e la strada Coolify in un clic.

Simmetric Team7 min di lettura10/03/2026

Un prodotto, tre forme di deployment

Simmetric Chat è una piattaforma multi-servizio - un server API, un collector per l'elaborazione dei documenti, un servizio widget incorporabile, un frontend e un database PostgreSQL - quindi la storia del deployment riguarda la scelta del packaging giusto per il vostro ambiente, non il cablaggio manuale dei servizi. Esistono tre vie di produzione, e tutti gli artefatti vivono nella directory docker/ del repository:

  • Compose multi-contenitore - sei servizi, ciascuno nel proprio contenitore con healthcheck. Il layout di produzione raccomandato, con isolamento per servizio e riavvii indipendenti.
  • Contenitore singolo all-in-one - una sola immagine che esegue tutto: Postgres, server, collector e widget supervisionati da supervisord. Pensata per host air-gapped e macchine demo, dove spostare più immagini attraverso il perimetro è la parte costosa.
  • Coolify - una via PaaS self-hosted con terminazione TLS di Traefik, in cui la piattaforma viene clonata e compilata per voi a partire da un file Compose tarato sulle convenzioni di Coolify.

Tutte le immagini sono build multi-stage su basi Node 24, e ogni servizio espone un healthcheck da cui dipende l'ordine di avvio dello stack. Questo articolo percorre ciascuna via, il modello di configurazione e i dettagli che mordono in produzione.

Lo stack Compose a sei servizi

Il file Compose di produzione definisce sei servizi attivi:

  • frontend - costruito come app React 19 da un builder Node, poi servito da nginx, che termina TLS con certificati auto-firmati generati alla build dell'immagine, serve la SPA e fa da proxy per /api e /widget verso i servizi di backend.
  • server - l'API Express 5: autenticazione, RBAC, orchestrazione delle chat e l'agente ReAct. È il servizio con cui parlano i vostri utenti.
  • collector - la pipeline documentale: parsing, chunking ed embedding degli upload. Costruito su una base Node slim invece che Alpine, perché le sue dipendenze native (LanceDB, i transformer di embedding locali, i binding canvas) richiedono glibc.
  • widget - il servizio del widget di chat incorporabile per i siti rivolti all'esterno.
  • postgres - PostgreSQL 16 con l'estensione pgvector inclusa, così il provider vettoriale predefinito non richiede un servizio di database vettoriale separato.
  • redis - la cache opzionale di scaling, abilitata con persistenza append-only ed eviction LRU. Ogni consumatore di Redis degrada con grazia in memoria, quindi questo servizio può stare dentro o fuori senza problemi.

Due servizi sono deliberatamente disabilitati nel file standard invece che assenti: ollama e qdrant. I loro blocchi arrivano commentati, e abilitarli è togliere il commento a una riga più una variabile - OLLAMA_BASE_URL=http://ollama:11434 per l'host dei modelli locali, oppure VECTOR_DB_PROVIDER=qdrant se volete uno store vettoriale remoto invece del predefinito LanceDB su file. I predefiniti commentati tengono un'installazione minima davvero minima.

L'ordine di avvio è governato dagli healthcheck: sonde basate su wget colpiscono GET /api/health su server e collector, /health sul widget, pg_isready su Postgres e redis-cli ping su Redis, e il frontend attende che il server si dichiari healthy prima di partire.

Portare su lo stack è un solo comando:

bash
docker compose -f docker/docker-compose.yml up --build -d

Il contenitore singolo all-in-one

La seconda via comprime l'intera piattaforma in una sola immagine. Un unico Dockerfile compila PostgreSQL 16 con l'estensione pgvector compilata da sorgente alla build dell'immagine, e supervisord esegue insieme, dentro il contenitore, database, server, collector e widget. Le porte 3000, 3210 e 3211 sono esposte, e l'healthcheck interroga /api/health del server su localhost.

bash
docker build -f docker/Dockerfile -t simmetric-chat-all-in-one .
docker run -d -p 3000:3000 -p 3210:3210 -p 3211:3211 simmetric-chat-all-in-one

L'immagine all-in-one è la storia dell'air-gap: un solo artefatto da far attraversare il perimetro, nessun pull da registry al momento dell'installazione, e tutto - dal database alla UI di chat - che si avvia con un solo docker run. I sorgenti di pgvector sono compilati e installati offline durante la build proprio perché l'immagine risultante non abbia dipendenze di rete in fase di installazione. I suoi valori predefiniti di ambiente sono segnaposto da scaffolding per design - l'operatore sovrascrive i segreti veri via .env o -e, e l'immagine non finge mai di essere production-safe appena uscita dalla scatola.

Un confine vale la pena conoscere: il plugin enterprise non viene mai cotto dentro nessuna immagine. È deliberato - mantiene le immagini AGPL della community libere da codice proprietario e consente ai clienti air-gapped di ricevere il plugin come tarball separato e verificabile, montato via bind al momento dell'esecuzione.

La via Coolify

Coolify trasforma lo stack in un deployment PaaS: clona il repository, compila le immagini, collega la rete interna e mette Traefik davanti per la terminazione TLS. Un file Compose dedicato automatizza ciò che il Compose standard lascia all'operatore - build delle immagini, networking interno, volumi, healthcheck e il mount del plugin enterprise.

Applica anche i segreti richiesti al momento del deploy con la sintassi :?: JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, POSTGRES_PASSWORD, APP_URL e ALLOWED_ORIGINS. Coolify si rifiuta di fare il deployment quando manca uno di essi, il che trasforma una classe di deployment mezza-configurati in un errore visibile prima che qualcosa parta.

Le differenze rispetto allo stack standard derivano dalla presenza di Traefik: i servizi applicativi non pubblicano porte sull'host (assegnate il dominio al servizio frontend, porta 80), la configurazione nginx è solo HTTP perché Traefik termina TLS, e il socket Docker non è montato per default - serve solo per il flusso di login di Ollama Cloud. Un runbook passo-passo copre i passaggi manuali: rsync dell'albero enterprise, riempimento dei segreti e inserimento della chiave di licenza.

Configurazione: un .env, montato da Compose

I contenitori ricevono la configurazione tramite env_file di Compose, che punta al .env nella radice del repository - lo stesso file di configurazione runtime unico che la piattaforma usa ovunque. Lo stack si avvia con i soli predefiniti a livello compose se il file è assente: sufficiente per un primo smoke test, non per la produzione.

Due segreti sono strettamente richiesti, e il server esce senza di essi:

env
# .env alla radice - richiesti in produzione
JWT_SECRET=...          # openssl rand -hex 32
COLLECTOR_SECRET=...    # openssl rand -hex 32

ENCRYPTION_KEY e API_KEY_HMAC_SECRET non affatto bisogno di essere pre-provisionati: agli avvii di produzione, l'entrypoint del server li genera una volta, li persiste nel volume server-storage, li ripristina a ogni riavvio, e preferisce un valore fornito dall'operatore quando esiste. Il provisioning non rigenera mai sopra materiale di chiave esistente e fallisce rumorosamente prima di qualsiasi passo sul database, così un volume mezza-scritto non può diventare in silenzio una chiave diversa.

C'è una trappola specifica di Compose che vale la pena nominare perché degrada i deployment in silenzio: non aggiungete voci di passthrough come LICENSE_KEY=${LICENSE_KEY:-} ai servizi di produzione. L'interpolazione della shell risolve una variabile non impostata nella stringa vuota, e una voce environment esplicitamente vuota sovrascrive il valore che arriva via env_file - così un LICENSE_KEY interpolato vuoto trasforma un deployment Enterprise in Community, e un ENCRYPTION_KEY interpolato vuoto sovrascrive l'auto-provisioning dell'entrypoint. Lasciate quelle chiavi al solo env_file.

I dettagli che contano in produzione

  • Il collector richiede glibc. Il collector gira su una base slim (Debian), non Alpine, perché LanceDB, i transformer di embedding locali e canvas richiedono tutti glibc. Non "semplificate" ad Alpine - la build fallisce.
  • Gli URL del database hanno tre forme. Dentro la rete Compose il server parla con postgres:5432 (cablato nell'environment del compose); il predefinito del .env alla radice punta a host.docker.internal per server sul lato host; l'immagine all-in-one usa localhost perché Postgres gira nello stesso contenitore.
  • Le migrazioni sono solo additive. L'entrypoint del server esegue prisma migrate deploy a ogni avvio, quindi far rotolare le immagini in avanti è sicuro per politica; tornare indietro significa fare il redeploy di un'immagine nota come buona, e il rollback del codice non ripristina le migrazioni applicate.
  • Lo scaling attende il provisioning. L'auto-provisioning dei segreti deve completarsi una volta prima che il server scalì oltre una replica - repliche al primo avvio in concorrenza potrebbero generare chiavi divergenti. L'istanza singola è il predefinito documentato; lo scaling multi-istanza ha una sua guida dedicata.
  • Gli endpoint di salute sono esterni. /api/health restituisce un oggetto di stato con verifiche per dipendenza ed è adatto a probe di uptime esterni e verifiche dei load balancer; i log strutturati vanno su stdout per docker compose logs -f <servizio>.

Le immagini rilasciate sono pubblicate su GHCR (server, frontend, collector, widget e all-in-one, taggate latest più la versione), così un deployment può fissare una versione invece di compilare in locale - anche se docker compose up --build a partire dal repository resta la via completamente self-hosted.

Dal clone alla piattaforma in esecuzione

Il percorso più breve, da capo a fondo:

  1. Clonate il repository e copiate .env.example in .env.
  2. Impostate JWT_SECRET e COLLECTOR_SECRET (l'entrypoint provisionsa da solo le chiavi di crittografia).
  3. docker compose -f docker/docker-compose.yml up --build -d.
  4. Seguite docker compose logs -f server fino a completamento di migrazioni e seeding, poi aprite il frontend e accedete.

Per gli host air-gapped, sostituite l'immagine all-in-one e il flusso di trasferimento via tarball. Per Coolify, importate il repository e riempite i segreti richiesti - la piattaforma fa il resto del deployment.

Leggi la guida completa nella documentazione →

Articoli correlati