Skip to content

Operations

1. Deployment

PropertyValue
Base imagedebian:12-slim (standalone Bun binary)
Containere.g. dev-nx-signal
Internal port3000 (external 31090)
Networknx-network (external, shared)
Replicashorizontally scalable (state is in Redis + DB, not the pod)
Snowflake ID9
Migration moderun-on-boot seeds (alwaysRun) — permissions + role-permissions only
HealthGET /v1/api/signal/health (public, from VerifierApplication)

Volumes

Host PathContainer PathPurpose
packages/signal/dist/bin/app/binCompiled binary (bun run rebuild)
packages/signal/resources/app/resourcesBanner, test client HTML
infrastructure/deployments/develop/signal/start.sh/app/start.shStartup script

Traefik — dual routing

Signal exposes two routers sharing one backend (port 3000):

RouterRuleMiddlewareWhy
signal-restPathPrefix(/v1/api/signal)rate-limit, circuit-breaker, security-headersStandard protection for short-lived HTTP requests
signal-wsPathPrefix(/stream)nonePersistent WS connections break under request-rate limiting / circuit-breaking
yaml
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.signal-rest.rule=PathPrefix(`/v1/api/signal`)"
  - "traefik.http.routers.signal-rest.middlewares=rate-limit@file,circuit-breaker@file,security-headers@file"
  - "traefik.http.routers.signal-ws.rule=PathPrefix(`/stream`)"
  - "traefik.http.services.signal.loadbalancer.server.port=3000"

The separate WS router (no middleware) is the key deployment invariant. See ADR-0003 and ADR-0001.

Redis bus

Signal's WebSocketServerHelper and every publisher's WebSocketEmitter must point at the same Redis instance/cluster (APP_ENV_WEBSOCKET_REDIS_*):

ServiceComponentRole
SignalWebSocketServerHelpersubscribe + publish (full server)
Sale / PaymentWebSocketEmitterpublish only

Multi-instance fan-out

ScenarioBehavior
Local deliveryTarget client on this instance → delivered directly (no Redis round-trip)
Remote deliveryTarget on another instance → publish to Redis, owning instance delivers
Broadcast / roomPublished to Redis; every instance delivers to its local matching clients

2. Observability

SignalSourceWhere to look
Logsstdout (structured key-value)kubectl logs / Loki
HealthGET /v1/api/signal/healthGateway portal / Traefik check (30s)
OpenAPIGET /v1/api/signal/doc, /explorerScalar UI
Tracesnone (no-op)

Key log lines

AreaWhat it tells you
Kafka consumertopic / partition / offset per message; broker connect/disconnect
Notification pushtopic / room / recipientId per WS emit; warns when emitter not ready
Handshakewarns on missing/invalid public key or unsupported auth type

3. Security

ConcernMitigation
AuthN (REST)JWT + Basic, verified via remote JWKS
AuthN (WS)type=Bearer JWT verified by JWKSVerifierTokenService at handshake
AuthZWebSocketClient.* permissions on client-mgmt routes; /notifications scoped by JWT subject
E2E encryptionMandatory ECDH P-256 + AES-256-GCM on /stream (requireEncryption: true); per-client ephemeral keys (forward secrecy), deleted on disconnect
Plaintext exceptionsonly connected and error events
TLSwss:// terminated at Traefik in production
Public endpoints/health, /doc, /openapi.json, /explorer, and GET /socket/websocket/clients/status
Room ACLcurrently passthrough (accepts all rooms) — merchant-scoped ACL is a known TODO
Networkinternal port 3000 not host-exposed; access only via Traefik

4. Runbook

4.1 Alert classes

AlertTriggerCheckFixEscalate
signalConsumerStalledno offset progress on signal.activity-notificationconsumer logs / lagrestart pod; verify brokers + SASLon-call backend
signalWsEmitterNotReadyrepeated "WebSocket emitter not ready" warningsRedis connectivitycheck APP_ENV_WEBSOCKET_REDIS_*, Redis healthon-call SRE
signalHandshakeRejectsspike in handshake rejectionsidentity JWKS reachability, clock skewverify identity URL + APP_ENV_WEBSOCKET_ECDH_INFO parityon-call backend
signalDuplicateNotificationsduplicate rows after redeliveryconsumer offset/commit logsde-dup query; mitigate (no built-in dedup — see ADR-0002)on-call backend

4.2 Common operations

OperationCommand
Tail logskubectl logs -n <ns> -f deploy/signal
Check WS readinesscurl /v1/api/signal/socket/websocket/clients/status
Inspect connected clientsGET /socket/websocket/clients (JWT + permission)
Replay notificationsre-publish to signal.activity-notification (note: creates duplicate rows)
Reseed permissionsrun signal migrations (idempotent) — operator-run only

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.