Retour à la documentation
Guide 09

Déploiement

Simmetric Chat est air-gap capable et local-first. Cinq cibles de déploiement : Docker Compose multi-container (recommandée pour la production), container unique all-in-one (environnements air-gapped), stack géré Coolify, override dev (hot-reload sur Doc…

5 min de lecture

Simmetric Chat est air-gap capable et local-first. Cinq cibles de déploiement : Docker Compose multi-container (recommandée pour la production), container unique all-in-one (environnements air-gapped), stack géré Coolify, override dev (hot-reload sur Docker) et desktop Tauri. Tous les artefacts vivent dans docker/ ; le shell desktop dans src-tauri/. Les images publiées sur GHCR sont uniquement amd64 (linux/amd64 - les builds arm64 émulées via QEMU se bloquaient ; sur arm64 on fait un self-build local depuis l'hôte natif). Tous les containers utilisent un healthcheck wget (--spider) : server GET /api/health, collector GET /api/health sur 3210, widget GET /health sur 3211, Postgres pg_isready, Redis redis-cli ping.

Prérequis

  • Node.js ≥ 24, pnpm 11.24.0, PostgreSQL 16.
  • Pour l'embedding local : ~2 GB RAM par all-MiniLM-L6-v2 (Xenova, CPU).
  • Pour le LLM local : Ollama avec modèle(s) pré-téléchargés (gemma4:latest par défaut).
  • Pour l'OCR vision : Ollama avec modèle vision (glm-ocr:latest par défaut).

Docker Compose (production)

Le compose de production (docker/docker-compose.yml) définit 6 services actifs : frontend (nginx, :80/:443), server (:3000), collector (:3210), widget (:3211), postgres (16, image pgvector/pgvector:pg16), redis (7-alpine, cache de scaling optionnel). ollama et qdrant sont désactivés par défaut (blocs commentés : décommente + définis OLLAMA_BASE_URL / VECTOR_DB_PROVIDER=qdrant pour les utiliser).

bash
cp .env.example .env
# Modifie .env : JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, DATABASE_URL, LICENSE_KEY (si Enterprise)
docker compose -f docker/docker-compose.yml up -d

Env critiques de production (voir 03 - Configuration) :

  • JWT_SECRET - obligatoire, matériel cryptographique (dérive ENCRYPTION_KEY si unset).
  • COLLECTOR_SECRET - obligatoire, doit matcher entre server et collector.
  • WIDGET_API_KEY - obligatoire pour le widget, doit matcher entre widget et server.
  • DATABASE_URL - PostgreSQL 16.
  • LICENSE_KEY + LICENSE_SECRET - uniquement Enterprise.
  • ENCRYPTION_KEY (base64 32 byte) - recommandé pour la persistance data-at-rest au-delà des rotations de JWT_SECRET.
Rotation des clés : définis LEGACY_PREVIOUS_ENCRYPTION_KEYS (CSV base64 32-byte)
pour maintenir la decryptabilité des backups/données anciennes pendant la rotation. Il n'existe pas
de job automatique de re-encryption : procédure manuelle decrypt-old → re-encrypt-new.

Auto-provisioning des clés (entrypoint server)

L'entrypoint de production (docker/entrypoint-server.sh) exécute à chaque boot : le provisioning de ENCRYPTION_KEY et API_KEY_HMAC_SECRET avec la précédence valeur operator-supplied > valeur persistée dans le volume server-storage > généré une fois et persisté (jamais régénéré sur du matériel existant) ; puis prisma migrate deploy + seed et le démarrage du serveur. Les configurations dev (docker-compose.dev.yml) sautent le provisioning et gardent le fallback scrypt.

Volumes & persistance

  • PostgreSQL : volume dédié (pgdata).
  • LanceDB : ${STORAGE_PATH}/vectors/lancedb (collector).
  • File storage : ${STORAGE_PATH} (collector, documents/avatar/uploads).
  • Backup : destinations configurées par-job (disk/S3/Dropbox/...).

Health check post-deploy

Les containers s'auto-vérifient via wget (voir intro). Checks manuels équivalents :

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 unique all-in-one

docker/Dockerfile construit une image unique supervisée par supervisord qui exécute PostgreSQL 16 (pgvector compilé offline pour l'air-gap), server, collector et widget. Il expose 3000/3210/3211, utilise DATABASE_URL sur localhost, healthcheck sur /api/health. Pensée pour les environnements air-gapped/offline/demo : les env par défaut sont des placeholder - surcharge-les via .env ou -e.

Coolify

docker/docker-compose.coolify.yml + docker/nginx.coolify.conf automatisent le build des images, le networking interne, les volumes, les healthchecks et le mount du plugin enterprise (chemin hôte absolu ${ENTERPRISE_PLUGIN_PATH:-/opt/simmetric-enterprise}). Les secrets requis utilisent la syntaxe :? (Coolify refuse le deploy s'ils manquent) : JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, POSTGRES_PASSWORD, APP_URL, ALLOWED_ORIGINS. Différences avec le compose de base : pas de container_name, aucune porte hôte sur les apps (ingress confié à Traefik, HTTP-only - TLS terminé par Traefik), ENCRYPTION_KEY/API_KEY_HMAC_SECRET non interpolées (auto-provisionnées dans le volume).

Développement local

bash
pnpm install
pnpm db:generate      # Prisma client (+ fix symlink Prisma/pnpm)
pnpm --filter server db:migrate   # applique les migrations
pnpm dev              # turborepo: server + collector + widget + frontend en parallèle

Détails dans 10 - Development.

Override dev (Docker)

docker/docker-compose.dev.yml monte les sources sur le compose de production pour le hot-reload (NODE_ENV=development). docker-compose.infra.yml démarre uniquement Postgres/Qdrant avec l'app en exécution sur l'hôte.

Desktop (Tauri)

src-tauri/ - app desktop qui wrappe le frontend. Build avec pnpm tauri build. Configure src-tauri/tauri.conf.json pour l'URL server distante ou le bundle local.

Reverse proxy (Nginx)

Points clés :

  • SSE : désactiver le buffering (proxy_buffering off;, X-Accel-Buffering: no déjà envoyé par le server/widget).
  • Timeout : LLM locaux lents → proxy_read_timeout élevé (≥ 600s, l'agent watchdog par défaut est 10 min).
  • Upload : client_max_body_size ≥ 100 MB (limite multer documents).
  • CORS : l'allowlist est gérée côté app (ALLOWED_ORIGINS) ; le widget a un CORS dynamique per-origin (allowedOrigins).

Backup en production

  1. Crée une destination (POST /api/backup-destinations).
  2. Crée un job (POST /api/backup-jobs) avec cron frequency/time + retentionDays.
  3. Vérifie avec POST /backup-destinations/:id/test et un run manuel POST /backup-jobs/:id/run.
  4. Restore : POST /api/backups/restore/:logId (dry-run conseillé avant : /restore/:logId/dry-run). Atomicité via psql --single-transaction.

Voir 05 - Feature Guide § Backup.

Sécurité de production

  • Change le mot de passe admin bootstrap ; définis ALLOW_REGISTRATION=false en prod (self-registration fermée).
  • DLP_ENABLED défaut "true" en v0.22 - redaction input/output chat sur 10 patterns built-in (voir 05 - Feature Guide) ; utilise DLP_BYPASS_ROLES pour exempter des rôles sélectionnés.
  • Active audit_log_immutable (Enterprise) pour un audit append-only.
  • HTTPS terminé au reverse proxy ; headers helmet actifs sur le server (relâchés sur le widget pour l'embedding).
  • Rate limit déjà configurés (voir 04 - API Reference § Rate limit) : la limite chat est désormais gérée par le budget tracker de l'agent (concurrence per-utilisateur, wallclock, token) plus le filet de protection global 200 req/min.