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.
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
/apie/widgetverso 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:
docker compose -f docker/docker-compose.yml up --build -dIl 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.
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-oneL'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 alla radice - richiesti in produzione
JWT_SECRET=... # openssl rand -hex 32
COLLECTOR_SECRET=... # openssl rand -hex 32ENCRYPTION_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.envalla radice punta ahost.docker.internalper server sul lato host; l'immagine all-in-one usalocalhostperché Postgres gira nello stesso contenitore. - Le migrazioni sono solo additive. L'entrypoint del server esegue
prisma migrate deploya 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/healthrestituisce 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 perdocker 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:
- Clonate il repository e copiate
.env.examplein.env. - Impostate
JWT_SECRETeCOLLECTOR_SECRET(l'entrypoint provisionsa da solo le chiavi di crittografia). docker compose -f docker/docker-compose.yml up --build -d.- Seguite
docker compose logs -f serverfino 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.

