Zurück zur Dokumentation
Guide 09

Deployment

Simmetric Chat ist air-gap capable und local-first. Fünf Deployment-Ziele: Docker Compose multi-container (empfohlen für Produktion), Single-Container all-in-one (Air-Gap-Umgebungen), verwalteter Coolify-Stack, Dev-Override (Hot-Reload auf Docker) und De…

4 Min. Lesezeit

Simmetric Chat ist air-gap capable und local-first. Fünf Deployment-Ziele: Docker Compose multi-container (empfohlen für Produktion), Single-Container all-in-one (Air-Gap-Umgebungen), verwalteter Coolify-Stack, Dev-Override (Hot-Reload auf Docker) und Desktop Tauri. Alle Artefakte leben in docker/; die Desktop-Shell in src-tauri/. Die auf GHCR veröffentlichten Images sind nur amd64 (linux/amd64 - die via QEMU emulierten arm64-Builds hingen; auf arm64 macht man einen lokalen Self-Build vom nativen Host). Alle Container nutzen wget-Healthchecks (--spider): server GET /api/health, collector GET /api/health auf 3210, widget GET /health auf 3211, Postgres pg_isready, Redis redis-cli ping.

Voraussetzungen

  • Node.js ≥ 24, pnpm 11.24.0, PostgreSQL 16.
  • Für lokales Embedding: ~2 GB RAM pro all-MiniLM-L6-v2 (Xenova, CPU).
  • Für lokales LLM: Ollama mit vorab gepullten Modell(en) (gemma4:latest Standard).
  • Für Vision-OCR: Ollama mit Vision-Modell (glm-ocr:latest Standard).

Docker Compose (Produktion)

Das Produktions-Compose (docker/docker-compose.yml) definiert 6 aktive Services: frontend (nginx, :80/:443), server (:3000), collector (:3210), widget (:3211), postgres (16, Image pgvector/pgvector:pg16), redis (7-alpine, optionaler Scaling-Cache). ollama und qdrant sind standardmäßig deaktiviert (kommentierte Blöcke: auskommentieren + OLLAMA_BASE_URL / VECTOR_DB_PROVIDER=qdrant setzen, um sie zu nutzen).

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

Kritische Produktions-Envs (siehe 03 - Configuration):

  • JWT_SECRET - erforderlich, kryptographisches Material (leitet ENCRYPTION_KEY ab wenn unset).
  • COLLECTOR_SECRET - erforderlich, muss zwischen Server und Collector übereinstimmen.
  • WIDGET_API_KEY - erforderlich für das Widget, muss zwischen Widget und Server übereinstimmen.
  • DATABASE_URL - PostgreSQL 16.
  • LICENSE_KEY + LICENSE_SECRET - nur Enterprise.
  • ENCRYPTION_KEY (base64 32 Byte) - empfohlen für persistente Data-at-Rest über JWT_SECRET-Rotationen hinaus.
Key-Rotation: Setze LEGACY_PREVIOUS_ENCRYPTION_KEYS (CSV base64 32-Byte), um die Entschlüsselbarkeit alter Backups/Daten während der Rotation zu erhalten. Es gibt keinen automatischen Re-Encryption-Job: manuelle Prozedur decrypt-old → re-encrypt-new.

Auto-Provisioning der Keys (Server-Entrypoint)

Der Produktions-Entrypoint (docker/entrypoint-server.sh) führt bei jedem Boot aus: Provisioning von ENCRYPTION_KEY und API_KEY_HMAC_SECRET mit der Priorität operator-supplied Wert > im Volume server-storage persistierter Wert > einmal generiert und persistiert (nie auf bestehendem Material neu generiert); dann prisma migrate deploy + Seed und der Server-Start. Die Dev-Konfigurationen (docker-compose.dev.yml) überspringen das Provisioning und behalten den scrypt-Fallback.

Volumes & Persistenz

  • PostgreSQL: dediziertes Volume (pgdata).
  • LanceDB: ${STORAGE_PATH}/vectors/lancedb (Collector).
  • File Storage: ${STORAGE_PATH} (Collector, Dokumente/Avatare/Uploads).
  • Backup: pro-Job konfigurierte Destinationen (Disk/S3/Dropbox/...).

Health Check nach dem Deploy

Die Container verifizieren sich selbst via wget (siehe Intro). Äquivalente manuelle Checks:

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

Single-Container all-in-one

docker/Dockerfile baut ein einziges Image, das von supervisord überwacht wird und PostgreSQL 16 (pgvector offline für Air-Gap kompiliert), Server, Collector und Widget ausführt. Es exponiert 3000/3210/3211, nutzt DATABASE_URL auf localhost, Healthcheck auf /api/health. Gedacht für Air-Gap-/Offline-/Demo-Umgebungen: Die Default-Envs sind Platzhalter - überschreibe sie via .env oder -e.

Coolify

docker/docker-compose.coolify.yml + docker/nginx.coolify.conf automatisieren den Image-Build, das interne Networking, Volumes, Healthchecks und den Mount des Enterprise-Plugins (absoluter Host-Pfad ${ENTERPRISE_PLUGIN_PATH:-/opt/simmetric-enterprise}). Die erforderlichen Secrets nutzen die Syntax :? (Coolify verweigert das Deployment wenn sie fehlen): JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, POSTGRES_PASSWORD, APP_URL, ALLOWED_ORIGINS. Unterschiede zum Basis-Compose: kein container_name, kein Host-Port auf den Apps (Ingress via Traefik, HTTP-only - TLS von Traefik terminiert), ENCRYPTION_KEY/API_KEY_HMAC_SECRET nicht interpoliert (im Volume auto-provisioniert).

Lokale Entwicklung

bash
pnpm install
pnpm db:generate      # Prisma Client (+ Fix Prisma/pnpm Symlink)
pnpm --filter server db:migrate   # Migrationen anwenden
pnpm dev              # turborepo: server + collector + widget + frontend parallel

Details in 10 - Development.

Dev-Override (Docker)

docker/docker-compose.dev.yml mountet die Quellen auf das Produktions-Compose für Hot-Reload (NODE_ENV=development). docker-compose.infra.yml startet nur Postgres/Qdrant, während die App auf dem Host läuft.

Desktop (Tauri)

src-tauri/ - Desktop-App, die das Frontend wrappt. Build mit pnpm tauri build. Konfiguriere src-tauri/tauri.conf.json für Remote-Server-URL oder lokales Bundle.

Reverse Proxy (Nginx)

Wichtige Punkte:

  • SSE: Buffering deaktivieren (proxy_buffering off;, X-Accel-Buffering: no wird bereits vom Server/Widget gesendet).
  • Timeout: langsame lokale LLMs → hoher proxy_read_timeout (≥ 600s, der Standard-Agent-Watchdog ist 10 min).
  • Upload: client_max_body_size ≥ 100 MB (Multer-Limit Dokumente).
  • CORS: Die Allowlist wird app-seitig verwaltet (ALLOWED_ORIGINS); das Widget hat dynamisches Per-Origin-CORS (allowedOrigins).

Backup in Produktion

  1. Destination erstellen (POST /api/backup-destinations).
  2. Job erstellen (POST /api/backup-jobs) mit Cron frequency/time + retentionDays.
  3. Verifizieren mit POST /backup-destinations/:id/test und einem manuellen Run POST /backup-jobs/:id/run.
  4. Restore: POST /api/backups/restore/:logId (Dry-Run empfohlen vorher: /restore/:logId/dry-run). Atomarität via psql --single-transaction.

Siehe 05 - Feature Guide § Backup.

Produktionssicherheit

  • Ändere das Bootstrap-Admin-Passwort; setze ALLOW_REGISTRATION=false in Prod (Self-Registration geschlossen).
  • DLP_ENABLED Standard "true" in v0.22 - Redaction von Chat-Input/Output über 10 Built-in-Patterns (siehe 05 - Feature Guide); nutze DLP_BYPASS_ROLES, um ausgewählte Rollen auszunehmen.
  • Aktiviere audit_log_immutable (Enterprise) für Append-Only-Audit.
  • HTTPS am Reverse Proxy terminiert; helmet-Header auf dem Server aktiv (für das Widget beim Embedding gelockert).
  • Rate Limits bereits konfiguriert (siehe 04 - API Reference § Rate limit): Das Chat-Limit wird jetzt vom Budget Tracker des Agenten verwaltet (Per-User-Concurrency, Wallclock, Token) plus das globale Schutznetz von 200 req/min.