Bridge deployment and operations specification
Supported topologies
Single node
One Phoenix endpoint may serve health, server/app WebSockets, and HTTP v1 routes. This is suitable for development and trusted-network use. An internet-facing deployment requires TLS termination at an edge or reverse proxy.
Cluster behind a load balancer
Multiple bridge nodes may run behind round-robin HAProxy or another WebSocket-capable HTTP load balancer. Nodes form an Erlang cluster through either:
- gossip, the default local/Docker strategy;
- DNS polling for service-discovery environments; or
- no clustering for intentionally single-node tasks.
The load balancer does not need sticky sessions for HTTP v1:
- server registration is replicated through PubSub;
- HTTP pairing confirmation dispatches to the owner of the pending code;
- transfer consumers/uploaders attach through the cluster-wide transfer topic.
Legacy frontend WebSocket sessions naturally remain on the node that owns the socket.
Secure edge
A public edge must:
- redirect or require HTTPS/WSS;
- forward WebSocket upgrade headers;
- forward query strings and all supported HTTP methods;
- forward
Authorization,Content-Type,X-Crystal-Bridge-Token, andX-Crystal-Bridge-Body-Present; - disable caching for control, proxy, and transfer traffic;
- use timeouts compatible with long request/response streaming.
The committed secure CloudFront definitions advertise HTTP/2 to viewers,
enable compression, and disable application-response caching. The TLS HAProxy
example advertises h2,http/1.1. Plain Phoenix origins normally use HTTP/1.1.
Shared cluster configuration
All nodes in one logical bridge deployment must share:
- the Erlang distribution cookie;
BRIDGE_TOKEN_SECRET;BRIDGE_TOKEN_SALT;BRIDGE_HTTP_TRANSFER_TOKEN_SALT;- compatible credential lifetimes and transfer limits.
Changing a signing secret invalidates credentials issued under the old secret. Rotate with an explicit re-pairing plan.
Configuration contract
| Setting | Default | Purpose |
|---|---|---|
PORT |
4000 |
Phoenix HTTP/WebSocket listener. |
BRIDGE_CLUSTER_STRATEGY |
gossip |
gossip, dns_poll, or none. |
GOSSIP_PORT |
45892 |
Gossip cluster port. |
GOSSIP_MULTICAST_ADDR |
230.1.1.251 |
Gossip multicast address. |
BRIDGE_CLUSTER_DNS_NAME |
bridge.local |
DNS polling query. |
BRIDGE_CLUSTER_NODE_BASENAME |
bridge |
DNS cluster node basename. |
PAIRING_CODE_TTL |
300 seconds |
Pairing-code lifetime. |
MAX_PAIRING_ATTEMPTS |
3 |
Wrong-code attempt limit. |
SESSION_TIMEOUT |
3600 seconds |
Legacy session inactivity lifetime. |
BRIDGE_PAIRING_TOKEN_TTL_SECONDS |
2592000 |
Signed pairing credential lifetime. |
BRIDGE_HTTP_TRANSFER_TOKEN_TTL_SECONDS |
180 |
Body-transfer credential lifetime. |
BRIDGE_HTTP_TIMEOUT_MS |
120000 |
Legacy response and HTTP transfer timeout. |
BRIDGE_HTTP_MAX_BODY_BYTES |
104857600 |
HTTP v1 request/response byte limit. |
BRIDGE_MAX_PENDING_HTTP_TRANSFERS |
128 |
Intended per-node active transfer bound. |
BRIDGE_MAX_PENDING_HTTP_REQUESTS |
128 |
Per-app legacy response-waiter bound. |
BRIDGE_HTTP_COMPRESS_THRESHOLD_BYTES |
1024 |
Cowboy viewer gzip threshold. |
BRIDGE_SERVER_ID_CONFLICT_POLICY |
takeover |
takeover or reject. |
The HTTP transfer supervisor enforces the active limit atomically when starting temporary coordinator children.
Compression
The Python server requests identity encoding from the local application and removes stale encoding/length response headers. The viewer-facing Phoenix endpoint may negotiate gzip for responses at or above its threshold.
Brotli is edge-dependent. The Phoenix origin does not require an in-process Brotli implementation. A compatible edge may negotiate Brotli when its compression policy and response type allow it.
Health
GET /health returns:
{
"status": "healthy",
"node": "bridge@node",
"cluster_size": 2,
"active_servers": 1,
"active_sessions": 3
}
cluster_size is the current node plus connected Erlang nodes.
active_servers is the local node's replicated registry view.
active_sessions is the local node's ephemeral legacy-session count, not a
cluster aggregate.
Load balancers should use /health for origin checks. HAProxy statistics can
be exposed separately and must not be treated as a bridge API.
Logging and credentials
Proxy and edge logs must not capture pairing credentials, transfer bearer tokens, or request bodies. The bridge's common responses are no-store, but operators must also disable caching at the edge.
Production deployments must not use the development fallback token secret. WebSocket origin checks are permissive in the current mobile-oriented endpoint; deployments with a fixed browser origin should restrict allowed origins.
Recovery
- A failed bridge node is removed from the Erlang cluster by normal cluster membership behavior.
- A pending HTTP pairing owned by the failed node cannot be confirmed; request a new code.
- A transfer coordinator lost with its node fails that request; clients may decide whether a later request is safe to retry.
- A reconnecting server registers the same stable ID and becomes discoverable.
- A valid pairing credential survives bridge process/node changes because it is stateless and signed, provided the signing configuration is unchanged.
Operating guides
Command-oriented setup, Docker, TLS reverse-proxy, AWS, and cleanup procedures
remain in bridge-elixir/PRODUCTION_DEPLOYMENT.md. The bridge package
quickstart covers local first use. This specification defines required
behavior, not provider-specific deployment steps.