Relay · Operations
Deployment and operations
Relay ships an application runtime contract, not a managed service. The repository can build an Elixir release and a multi-stage container image. Your deployment owns image publication, PostgreSQL, R2, TLS termination, secrets, backups, rollout, rollback, and alerting.
Runtime dependencies
| Dependency | Contract |
|---|---|
| PostgreSQL 18 or newer | Stores the public registry, per-tenant schemas, Oban jobs, handoffs, audit rows, and the default search index. Native uuidv7() is required. |
| Cloudflare R2-compatible storage | Stores message attachments. The production runtime currently requires R2 credentials when using the default storage client. |
| HTTP and WebSocket proxy | Must preserve Phoenix WebSocket upgrades and forwarded HTTPS scheme. |
| OTLP collector | Optional operational dependency. The runtime defaults its trace exporter endpoint to http://localhost:4318. |
Postgres FTS requires pg_trgm. zhparser is recommended for Chinese segmentation but optional. When unavailable, Relay falls back to simple_bigram, logs a warning, and marks affected search responses as degraded.
Required production configuration
| Variable | Purpose |
|---|---|
DATABASE_URL | PostgreSQL connection URL |
SECRET_KEY_BASE | Phoenix signing and encryption secret |
CLOAK_KEY | Base64-encoded 32-byte AES-GCM key for tenant secrets |
PHX_SERVER=true | Starts the HTTP server in an Elixir release |
ADMIN_TOKEN | Protects /api/admin/*; without it, admin API requests cannot authenticate |
TONETIFY_RELAY_CORS_ORIGINS | Comma-separated browser origins; production fails closed for cross-origin requests when empty |
R2_ENDPOINT | R2 account endpoint |
R2_BUCKET | Attachment bucket |
R2_ACCESS_KEY_ID | Bucket-scoped access key |
R2_SECRET_ACCESS_KEY | Bucket-scoped secret |
Common optional settings include PORT, PHX_HOST, POOL_SIZE, DNS_CLUSTER_QUERY, METRICS_TOKEN, OTEL_EXPORTER_OTLP_ENDPOINT, TONETIFY_RELAY_WEBHOOK_URL, TONETIFY_RELAY_WEBHOOK_SECRET, upload and download URL TTLs, and SEARCH_CHINESE_MODE.
Set METRICS_TOKEN in every exposed production environment. When it is unset, /metrics is reachable without application-layer authentication. Network policy should restrict the endpoint even when a token is present.
Do not log ADMIN_TOKEN, CLOAK_KEY, R2 credentials, host webhook secrets, host-sync Basic credentials, tenant JWT secrets, or presigned attachment URLs.
Build and start
The repository includes server/Dockerfile, which builds the Mix release and starts bin/chataas. A non-container deployment can run the same release directly:
PHX_SERVER=true bin/chataas start The repository does not currently publish a versioned image, provide a supported Compose stack, or define a platform-neutral deployment manifest. Treat the Dockerfile as a build input, not a distributed product artifact.
Migrations
Run migrations before sending traffic to a new release:
bin/chataas eval "Chataas.Release.migrate()" This entrypoint applies public-schema migrations, discovers every tenant_% schema, and applies tenant migrations to each one. Migration traffic should use a direct PostgreSQL connection or a pool mode known to support DDL and advisory behavior.
Use forward-compatible expand and contract changes when old and new application versions can overlap. The runtime includes a public-schema rollback function, but there is no complete automated rollback contract for both public and tenant schemas. Restore rehearsals and downgrade compatibility remain operator responsibilities.
Health and deploy gates
| Endpoint | Meaning |
|---|---|
GET /livez | Process liveness without dependency checks |
GET /health | Legacy alias for liveness |
GET /ready or GET /readyz | Executes SELECT 1 against the primary database and returns 503 when unavailable |
A minimum deployment gate is:
curl -fsS "$BASE_URL/health"
curl -fsS "$BASE_URL/ready"
curl -fsS "$BASE_URL/api/channels" \
-H "Authorization: Bearer $TENANT_USER_JWT" Then connect a real WebSocket client, join tonetify-relay:<tenant_id>:<channel_id>, send msg:send with a unique nonce, and confirm the acknowledgement contains id and seq. Liveness and readiness alone do not prove tenant migration, authentication, fan-out, or message delivery.
Workers and durable handoffs
Relay runs Oban queues for WebSocket fan-out, host webhooks, adapter egress, user exports, governance cleanup, R2 deletion, and search reindexing. Message-side effects are first written to public.post_commit_handoffs, then drained by sink workers.
The periodic poller provides correctness after a best-effort immediate wakeup. Stale processing claims are recovered after a default five-minute threshold. dispatch_seq orders handoff claims; message order still comes from channel-local messages.seq.
Operators should watch queue availability, retry exhaustion, oldest pending handoff age, per-sink backlog, webhook dead letters, R2 deletion failures, reindex status, and database pool saturation. A healthy HTTP process can still have a growing delivery backlog.
Metrics and traces
GET /metrics exposes Phoenix, Ecto, VM, business-event, search, R2, and post-commit handoff series. Useful signals include:
- successful send, edit, retract, and delete throughput;
- join and catch-up activity;
- WebSocket and HTTP error buckets;
- handoff transition counts and backlog gauges;
- search, webhook, and object-deletion failures.
Typing is best-effort and intentionally absent from durable delivery and business metrics. A reconnect metric means a successful join carrying last_seq, not every lower-level socket reconnect.
Relay emits OpenTelemetry traces through OTLP. The repository does not provide dashboards, alert thresholds, paging policy, or service objectives as a supported product package.
Backup and recovery boundary
Back up PostgreSQL and the attachment bucket as one product data system. Database recovery must preserve the public registry, every tenant schema, Oban state, handoffs, audit rows, and search tables. Object recovery must preserve attachment keys referenced by those schemas.
The repository does not include a tested backup schedule, point-in-time recovery objective, cross-region plan, or restore verification suite. It also does not publish an SLA, RPO, or RTO. These gaps prevent the current runtime from being sold as managed, production-ready infrastructure without an operator layer.
Related: Data lifecycle and audit · Limits and errors · Relay overview