Volver a la documentación
Guía 09

Deployment

Simmetric Chat es air-gap capable y local-first. Cinco targets de deployment: Docker Compose multi-container (recomendada para producción), container único all-in-one (entornos air-gapped), stack gestionado Coolify, override dev (hot-reload sobre Docker)…

4 min de lectura

Simmetric Chat es air-gap capable y local-first. Cinco targets de deployment: Docker Compose multi-container (recomendada para producción), container único all-in-one (entornos air-gapped), stack gestionado Coolify, override dev (hot-reload sobre Docker) y desktop Tauri. Todos los artefactos viven en docker/; el shell desktop en src-tauri/. Las imágenes publicadas en GHCR son solo amd64 (linux/amd64 - los builds arm64 emulados vía QEMU se bloqueaban; en arm64 se hace self-build local desde el host nativo). Todos los containers usan healthcheck wget (--spider): server GET /api/health, collector GET /api/health en 3210, widget GET /health en 3211, Postgres pg_isready, Redis redis-cli ping.

Prerrequisitos

  • Node.js ≥ 24, pnpm 11.24.0, PostgreSQL 16.
  • Para embedding local: ~2 GB RAM por all-MiniLM-L6-v2 (Xenova, CPU).
  • Para LLM local: Ollama con modelo(s) pre-pulleados (gemma4:latest default).
  • Para OCR vision: Ollama con modelo vision (glm-ocr:latest default).

Docker Compose (producción)

El compose de producción (docker/docker-compose.yml) define 6 servicios activos: frontend (nginx, :80/:443), server (:3000), collector (:3210), widget (:3211), postgres (16, imagen pgvector/pgvector:pg16), redis (7-alpine, cache de scaling opcional). ollama y qdrant están deshabilitados por defecto (bloques comentados: descomenta + establece OLLAMA_BASE_URL / VECTOR_DB_PROVIDER=qdrant para usarlos).

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

Env críticos de producción (ver 03 - Configuration):

  • JWT_SECRET - obligatorio, material criptográfico (deriva ENCRYPTION_KEY si unset).
  • COLLECTOR_SECRET - obligatorio, debe matchar entre server y collector.
  • WIDGET_API_KEY - obligatorio para widget, debe matchar entre widget y server.
  • DATABASE_URL - PostgreSQL 16.
  • LICENSE_KEY + LICENSE_SECRET - solo Enterprise.
  • ENCRYPTION_KEY (base64 32 byte) - recomendado para persistencia data-at-rest más allá de rotaciones de JWT_SECRET.
Rotación de claves: establece LEGACY_PREVIOUS_ENCRYPTION_KEYS (CSV base64 32-byte) para mantener la decryptabilidad de backups/datos antiguos durante la rotación. No existe job automático de re-encryption: procedimiento manual decrypt-old → re-encrypt-new.

Auto-provisioning de claves (entrypoint server)

El entrypoint de producción (docker/entrypoint-server.sh) ejecuta en cada boot: provisioning de ENCRYPTION_KEY y API_KEY_HMAC_SECRET con precedencia valor operator-supplied > valor persistido en el volumen server-storage > generado una vez y persistido (nunca regenerado sobre material existente); luego prisma migrate deploy + seed y el arranque del servidor. Las configuraciones dev (docker-compose.dev.yml) saltan el provisioning y mantienen el fallback scrypt.

Volúmenes & persistencia

  • PostgreSQL: volumen dedicado (pgdata).
  • LanceDB: ${STORAGE_PATH}/vectors/lancedb (collector).
  • File storage: ${STORAGE_PATH} (collector, documentos/avatar/uploads).
  • Backup: destinos configurados por-job (disk/S3/Dropbox/...).

Health check post-deploy

Los containers se auto-verifican vía wget (ver intro). Checks manuales equivalentes:

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 único all-in-one

docker/Dockerfile construye una única imagen supervisada por supervisord que ejecuta PostgreSQL 16 (pgvector compilado offline para air-gap), server, collector y widget. Expone 3000/3210/3211, usa DATABASE_URL en localhost, healthcheck en /api/health. Pensada para entornos air-gapped/offline/demo: los env por defecto son placeholder - sobrescríbelos vía .env o -e.

Coolify

docker/docker-compose.coolify.yml + docker/nginx.coolify.conf automatizan build de imágenes, networking interno, volúmenes, healthchecks y el mount del plugin enterprise (path host absoluto ${ENTERPRISE_PLUGIN_PATH:-/opt/simmetric-enterprise}). Los secretos requeridos usan la sintaxis :? (Coolify rechaza el deploy si faltan): JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, POSTGRES_PASSWORD, APP_URL, ALLOWED_ORIGINS. Diferencias con el compose base: sin container_name, sin puerto host en las apps (ingress confiado a Traefik, HTTP-only - TLS terminado por Traefik), ENCRYPTION_KEY/API_KEY_HMAC_SECRET no interpoladas (auto-provisionadas en el volumen).

Desarrollo local

bash
pnpm install
pnpm db:generate      # Prisma client (+ fix symlink Prisma/pnpm)
pnpm --filter server db:migrate   # aplica las migraciones
pnpm dev              # turborepo: server + collector + widget + frontend en paralelo

Detalles en 10 - Development.

Override dev (Docker)

docker/docker-compose.dev.yml monta las fuentes sobre el compose de producción para hot-reload (NODE_ENV=development). docker-compose.infra.yml arranca solo Postgres/Qdrant con la app ejecutándose en el host.

Desktop (Tauri)

src-tauri/ - app desktop que wrappea el frontend. Build con pnpm tauri build. Configura src-tauri/tauri.conf.json para URL server remota o bundle local.

Reverse proxy (Nginx)

Puntos clave:

  • SSE: deshabilitar buffering (proxy_buffering off;, X-Accel-Buffering: no ya enviado por el server/widget).
  • Timeout: LLM locales lentos → proxy_read_timeout alto (≥ 600s, el agent watchdog default es 10 min).
  • Upload: client_max_body_size ≥ 100 MB (límite multer documentos).
  • CORS: la allowlist se gestiona del lado app (ALLOWED_ORIGINS); el widget tiene CORS dinámico per-origin (allowedOrigins).

Backup en producción

  1. Crea destino (POST /api/backup-destinations).
  2. Crea job (POST /api/backup-jobs) con cron frequency/time + retentionDays.
  3. Verifica con POST /backup-destinations/:id/test y un run manual POST /backup-jobs/:id/run.
  4. Restore: POST /api/backups/restore/:logId (dry-run recomendado antes: /restore/:logId/dry-run). Atomicidad vía psql --single-transaction.

Ver 05 - Feature Guide § Backup.

Seguridad de producción

  • Cambia la password admin bootstrap; establece ALLOW_REGISTRATION=false en prod (self-registration cerrada).
  • DLP_ENABLED default "true" en v0.22 - redaction input/output chat sobre 10 patrones built-in (ver 05 - Feature Guide); usa DLP_BYPASS_ROLES para eximir roles seleccionados.
  • Habilita audit_log_immutable (Enterprise) para audit append-only.
  • HTTPS terminado en el reverse proxy; headers helmet activos en el server (relajados en el widget para embedding).
  • Rate limit ya configurados (ver 04 - API Reference § Rate limit): el límite chat ahora lo gestiona el budget tracker del agente (concurrencia per-usuario, wallclock, token) más la red de protección global 200 req/min.