Platform

Self-hosting

The same image runs on your host or ours. This page is the operator’s view: what to set, what to put in front of it, and how to keep it running with nothing leaving the building.

Layout

  • One Python process runs the chat listener, the scheduler and the API with the dashboard in a single event loop.
  • The container listens on the loopback interface only; a reverse proxy (Nginx or Caddy) terminates TLS and forwards to it.
  • State lives in a data directory: the SQLite database, uploads, GeoIP data, generated keys and model weights. Point DATABASE_URL at Postgres for multi-host deployments; the additive migration set works on both.
  • The chat server (Synapse or another Matrix homeserver) can be anywhere reachable.

Environment

MATRIX_HOMESERVER, MATRIX_USER_ID, MATRIX_ACCESS_TOKENthe bot account
MATRIX_STANDUP_ROOM_IDthe default standup room
MATRIX_ADMIN_TOKENhomeserver admin token for provisioning and lookups; environment only, never persisted
SECRET_KEYa 64+ character random string signing dashboard sessions
DATABASE_URLsqlite by default, or a Postgres URL
BOOTSTRAP_ADMIN_MATRIX_IDthe first admin, promoted on startup
AI_PROVIDER and the provider keysee AI providers
INTEGRATION_VAULT_KEYthe key encrypting stored integration credentials
TZ, MAX_BODY_BYTES, AI_REASONING_MODELoptional

Reverse proxy

  • Forward X-Forwarded-For and X-Forwarded-Proto; without the latter the product cannot tell HTTP from HTTPS and skips the HSTS header.
  • Enable compression for proxied responses and HTTP/2; the dashboard pulls dozens of assets on a cold load.
  • Never compress or buffer the event stream endpoint; live updates are server-sent events.
  • Per-IP rate limiting is the proxy’s job; the product limits code requests and logins itself.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
gzip_proxied any;
http2 on;

Security headers and limits

Every response carries nosniff, frame denial, a no-referrer policy, a restrictive permissions policy, a two-year HSTS on TLS and a content-security policy. Request bodies are capped at 5 MiB by default. Every input is validated by schema before a handler runs; chat IDs are regex-checked before any homeserver call; outbound HTTP is pinned to the homeserver and the configured provider host.

Operate

  • Upgrade: pull, rebuild, restart. Migrations run on boot and skip what exists.
  • Back up the data directory nightly; for Postgres use pg_dump.
  • Health: a fast unauthenticated liveness endpoint for the container check and an admin-gated detailed one with uptime, database size and provider status.
  • GeoIP: run the refresh script weekly (keyless db-ip Lite, or MaxMind with a free key); a copy in the data volume is picked up on restart with no configuration.
  • The scheduler runs the workday: presence samples every five minutes, absence checks every fifteen, nightly roll-ups and score materialisation, digests and reports on their cadences.
docker compose up -d --build
docker exec inroots sqlite3 /app/data/standup.db ".backup /tmp/backup.db"
GEO_DATA_DIR=/app/data/geo scripts/update_geoip.sh

Air-gapped

With a self-hosted Matrix server, Ollama as the provider, a local IP database on the host, spoken replies left off and no integrations enabled, the product needs no internet at all. There is no telemetry, no usage beacon and no crash reporter calling out; zero bytes leave unless you configure a destination.