Blog
Tutoriels

Déployer Simmetric Chat avec Docker : guide complet

La carte complète du déploiement de Simmetric Chat - une pile Compose à six services, un conteneur all-in-one unique pour les hôtes air-gapped et la voie Coolify en un clic.

Simmetric Team7 min de lecture10/03/2026

Un produit, trois formes de déploiement

Simmetric Chat est une plateforme multi-services - un serveur API, un collector pour le traitement des documents, un service widget embarquable, un frontend et une base PostgreSQL - donc l'histoire du déploiement consiste à choisir le bon packaging pour votre environnement, pas à câbler les services à la main. Il existe trois voies de production, et tous les artefacts vivent dans le répertoire docker/ du dépôt :

  • Compose multi-conteneurs - six services, chacun dans son propre conteneur avec healthcheck. La disposition de production recommandée, avec isolation par service et redémarrages indépendants.
  • Conteneur unique all-in-one - une seule image qui exécute tout : Postgres, serveur, collector et widget supervisés par supervisord. Pensée pour les hôtes air-gapped et les machines de démo, où déplacer plusieurs images à travers le périmètre est la partie coûteuse.
  • Coolify - une voie PaaS self-hosted avec terminaison TLS par Traefik, où la plateforme est clonée et compilée pour vous à partir d'un fichier Compose taillé sur les conventions de Coolify.

Toutes les images sont des builds multi-stage sur des bases Node 24, et chaque service expose un healthcheck dont dépend l'ordre de démarrage de la pile. Cet article parcourt chaque voie, le modèle de configuration et les détails qui mordent en production.

La pile Compose à six services

Le fichier Compose de production définit six services actifs :

  • frontend - construit comme une app React 19 depuis un builder Node, puis servi par nginx, qui termine le TLS avec des certificats auto-signés générés au build de l'image, sert la SPA et fait proxy pour /api et /widget vers les services backend.
  • server - l'API Express 5 : authentification, RBAC, orchestration des chats et l'agent ReAct. C'est le service avec lequel parlent vos utilisateurs.
  • collector - la pipeline documentaire : parsing, chunking et embedding des uploads. Construit sur une base Node slim plutôt qu'Alpine, parce que ses dépendances natives (LanceDB, les transformers d'embedding locaux, les bindings canvas) exigent glibc.
  • widget - le service du widget de chat embarquable pour les sites tournés vers l'extérieur.
  • postgres - PostgreSQL 16 avec l'extension pgvector incluse, pour que le provider vectoriel par défaut ne requière pas un service de base vectorielle séparé.
  • redis - le cache optionnel de mise à l'échelle, activé avec persistance append-only et éviction LRU. Chaque consommateur de Redis dégrade gracieusement en mémoire, donc ce service peut être présent ou absent sans problème.

Deux services sont délibérément désactivés dans le fichier standard plutôt qu'absents : ollama et qdrant. Leurs blocs arrivent commentés, et les activer consiste à décommenter une ligne plus une variable - OLLAMA_BASE_URL=http://ollama:11434 pour l'hôte des modèles locaux, ou VECTOR_DB_PROVIDER=qdrant si vous voulez un store vectoriel distant au lieu du LanceDB sur fichier par défaut. Les défauts commentés gardent une installation minimale réellement minimale.

L'ordre de démarrage est gouverné par les healthchecks : des sondes basées sur wget frappent GET /api/health sur le serveur et le collector, /health sur le widget, pg_isready sur Postgres et redis-cli ping sur Redis, et le frontend attend que le serveur se déclare healthy avant de démarrer.

Monter la pile est une seule commande :

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

Le conteneur unique all-in-one

La seconde voie comprime toute la plateforme en une seule image. Un unique Dockerfile compile PostgreSQL 16 avec l'extension pgvector compilée depuis les sources au build de l'image, et supervisord exécute ensemble, dans le conteneur, base de données, serveur, collector et widget. Les ports 3000, 3210 et 3211 sont exposés, et le healthcheck interroge /api/health du serveur sur 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'image all-in-one est l'histoire de l'air-gap : un seul artefact à faire traverser le périmètre, aucun pull depuis un registry au moment de l'installation, et tout - de la base de données à l'UI de chat - qui démarre avec un seul docker run. Les sources de pgvector sont compilées et installées hors ligne pendant le build précisément pour que l'image résultante n'ait aucune dépendance réseau à l'installation. Ses valeurs d'environnement par défaut sont des placeholders d'échafaudage par conception - l'opérateur écrase les vrais secrets via .env ou -e, et l'image ne prétend jamais être production-safe telle quelle.

Une frontière mérite d'être connue : le plugin enterprise n'est jamais cuit dans aucune image. C'est délibéré - cela garde les images AGPL de la communauté libres de code propriétaire et permet aux clients air-gapped de recevoir le plugin comme tarball séparé et vérifiable, monté par bind au moment de l'exécution.

La voie Coolify

Coolify transforme la pile en déploiement PaaS : il clone le dépôt, compile les images, câble le réseau interne et place Traefik devant pour la terminaison TLS. Un fichier Compose dédié automatise ce que le Compose standard laisse à l'opérateur - build des images, networking interne, volumes, healthchecks et le montage du plugin enterprise.

Il applique aussi les secrets requis au moment du déploiement avec la syntaxe :? : JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, POSTGRES_PASSWORD, APP_URL et ALLOWED_ORIGINS. Coolify refuse de déployer quand l'un d'eux manque, ce qui transforme une classe de déploiements à moitié configurés en une erreur visible avant que quoi que ce soit ne démarre.

Les différences avec la pile standard découlent de la présence de Traefik : les services applicatifs ne publient pas de ports sur l'hôte (assignez le domaine au service frontend, port 80), la configuration nginx est HTTP uniquement parce que Traefik termine le TLS, et le socket Docker n'est pas monté par défaut - il ne sert qu'au flux de login d'Ollama Cloud. Un runbook pas-à-pas couvre les étapes manuelles : rsync de l'arbre enterprise, remplissage des secrets et insertion de la clé de licence.

Configuration : un seul .env, monté par Compose

Les conteneurs reçoivent la configuration via l'env_file de Compose, qui pointe vers le .env à la racine du dépôt - le même fichier de configuration runtime unique que la plateforme utilise partout. La pile démarre avec les seuls défauts au niveau compose si le fichier est absent : suffisant pour un premier smoke test, pas pour la production.

Deux secrets sont strictement requis, et le serveur quitte sans eux :

env
# .env à la racine - requis en production
JWT_SECRET=...          # openssl rand -hex 32
COLLECTOR_SECRET=...    # openssl rand -hex 32

ENCRYPTION_KEY et API_KEY_HMAC_SECRET n'ont pas du tout besoin d'être pré-provisionnés : au démarrage de production, l'entrypoint du serveur les génère une fois, les persiste dans le volume server-storage, les restaure à chaque redémarrage, et préfère une valeur fournie par l'opérateur quand elle existe. Le provisioning ne régénère jamais par-dessus du matériel de clé existant et échoue bruyamment avant toute étape sur la base de données, pour qu'un volume à moitié écrit ne puisse pas devenir silencieusement une clé différente.

Il y a un piège Compose spécifique qui mérite d'être nommé parce qu'il dégrade les déploiements en silence : n'ajoutez pas d'entrées de passthrough comme LICENSE_KEY=${LICENSE_KEY:-} aux services de production. L'interpolation du shell résout une variable non définie en chaîne vide, et une entrée environment explicitement vide écrase la valeur qui arrive via env_file - donc un LICENSE_KEY interpolé vide transforme un déploiement Enterprise en Community, et un ENCRYPTION_KEY interpolé vide écrase l'auto-provisioning de l'entrypoint. Laissez ces clés au seul env_file.

Les détails qui comptent en production

  • Le collector exige glibc. Le collector tourne sur une base slim (Debian), pas Alpine, parce que LanceDB, les transformers d'embedding locaux et canvas exigent tous glibc. Ne « simplifiez » pas vers Alpine - le build échoue.
  • Les URL de base de données ont trois formes. Dans le réseau Compose le serveur parle à postgres:5432 (câblé dans l'environnement du compose) ; le défaut du .env à la racine pointe vers host.docker.internal pour les serveurs côté hôte ; l'image all-in-one utilise localhost parce que Postgres tourne dans le même conteneur.
  • Les migrations sont uniquement additives. L'entrypoint du serveur exécute prisma migrate deploy à chaque démarrage, donc faire rouler les images en avant est sûr par politique ; revenir en arrière signifie redéployer une image connue comme bonne, et le rollback du code ne restaure pas les migrations appliquées.
  • La mise à l'échelle attend le provisioning. L'auto-provisioning des secrets doit se terminer une fois avant que le serveur ne scale au-delà d'une réplique - des répliques au premier démarrage en concurrence pourraient générer des clés divergentes. L'instance unique est le défaut documenté ; la mise à l'échelle multi-instance a son propre guide dédié.
  • Les endpoints de santé sont externes. /api/health renvoie un objet d'état avec des vérifications par dépendance et convient aux sondes d'uptime externes et aux vérifications des load balancers ; les logs structurés vont sur stdout pour docker compose logs -f <service>.

Les images publiées sont publiées sur GHCR (server, frontend, collector, widget et all-in-one, taguées latest plus la version), donc un déploiement peut épingler une version au lieu de compiler localement - même si docker compose up --build depuis le dépôt reste la voie entièrement self-hosted.

Du clone à la plateforme en marche

Le chemin le plus court, de bout en bout :

  1. Clonez le dépôt et copiez .env.example vers .env.
  2. Définissez JWT_SECRET et COLLECTOR_SECRET (l'entrypoint provisionne lui-même les clés de chiffrement).
  3. docker compose -f docker/docker-compose.yml up --build -d.
  4. Suivez docker compose logs -f server jusqu'à la fin des migrations et du seeding, puis ouvrez le frontend et connectez-vous.

Pour les hôtes air-gapped, remplacez par l'image all-in-one et le flux de transfert par tarball. Pour Coolify, importez le dépôt et remplissez les secrets requis - la plateforme fait le reste du déploiement.

Lire le guide complet dans la doc →

Articles associés