Standing up a fresh single-node relay by following deploy/compose/README.md produces a relay that the official desktop app cannot join, and whose "Pair Mobile Device" flow always fails. Both are gaps between deploy/compose and deploy/charts/buzz, and both surface as misleading errors that point away from the real cause.
Relay image: ghcr.io/block/buzz:sha-3a4bf51. Deployment: deploy/compose with BUZZ_COMPOSE_TLS=true, unmodified .env.example values except secrets and domain.
1. .env.example omits the desktop app's origin from BUZZ_CORS_ORIGINS
deploy/compose/.env.example sets:
BUZZ_CORS_ORIGINS=https://buzz.example.com
The Tauri desktop app's webview origin is tauri://localhost, so every REST call it makes is blocked by the browser. AddCommunityDialog → "Join an existing community" fails with "Load failed" — WebKit's generic fetch error. No WebSocket is ever opened, so the relay logs show zero connection attempts, which makes it look like an auth or network problem rather than CORS.
Reproduce against a relay deployed from the bundle:
# Allowed origin — header present
curl -sD- -o /dev/null -H "Origin: https://relay.example.com" \
https://relay.example.com/api/join-policy | grep -i access-control-allow-origin
# access-control-allow-origin: https://relay.example.com
# Desktop app's actual origin — no header, browser blocks the response
curl -sD- -o /dev/null -H "Origin: tauri://localhost" \
https://relay.example.com/api/join-policy | grep -i access-control-allow-origin
# (nothing)
crates/buzz-relay/src/config.rs already documents tauri://localhost as the expected value, so this looks like the example file simply drifted from the code comment.
Suggested fix — add the app origins to .env.example:
BUZZ_CORS_ORIGINS=https://buzz.example.com,tauri://localhost,http://tauri.localhost,https://tauri.localhost
2. The bundle advertises NIP-43 but ships no pairing sidecar, so /pair 404s
deploy/charts/buzz deploys buzz-pair-relay as its own workload (templates/pairing-relay.yaml) and wires BUZZ_PAIRING_RELAY_URL. deploy/compose has neither, but the main relay still advertises NIP-43 in its NIP-11 document.
desktop/src-tauri/src/commands/pairing.rs::resolve_pairing_relay_url keys off exactly that: with NIP-43 advertised and no pairing_relay_url present, it takes the LegacyPath branch and dials wss://<relay>/pair. The main relay has no such route, so pairing fails 100% of the time on a compose deployment:
curl -so /dev/null -w '%{http_code}\n' https://relay.example.com/pair
# 404
In the UI this renders as the QR code flashing on screen and immediately disappearing, with WebSocket connection failed: HTTP error: 404 Not Found.
Suggested fix — ship the sidecar in the compose bundle, the same way the chart does. A working local override, for reference:
# compose.pair.yml
services:
pair-relay:
image: ${BUZZ_IMAGE}
entrypoint: ["/usr/local/bin/buzz-pair-relay"] # image ENTRYPOINT is buzz-relay, so `command:` is silently ignored
environment:
BUZZ_PAIR_RELAY_BIND_ADDR: 0.0.0.0:5000
restart: unless-stopped
networks: [buzz-net]
{$BUZZ_DOMAIN} {
encode zstd gzip
@pair path /pair /pair/*
reverse_proxy @pair pair-relay:5000
reverse_proxy relay:3000
}
plus BUZZ_PAIRING_RELAY_URL=wss://<domain>/pair in .env so NIP-11 advertises it explicitly rather than relying on the legacy path convention. With that in place /pair returns 101 Switching Protocols and pairing completes normally.
One footgun worth a comment in the example: because the image's ENTRYPOINT is /usr/local/bin/buzz-relay, using command: to select the pairing binary silently starts a second main relay instead. It then contends for the Postgres pool and crash-loops, and the proxy returns 502 — an error that looks nothing like the actual mistake.
Happy to open a PR for either or both if that's useful.
Standing up a fresh single-node relay by following
deploy/compose/README.mdproduces a relay that the official desktop app cannot join, and whose "Pair Mobile Device" flow always fails. Both are gaps betweendeploy/composeanddeploy/charts/buzz, and both surface as misleading errors that point away from the real cause.Relay image:
ghcr.io/block/buzz:sha-3a4bf51. Deployment:deploy/composewithBUZZ_COMPOSE_TLS=true, unmodified.env.examplevalues except secrets and domain.1.
.env.exampleomits the desktop app's origin fromBUZZ_CORS_ORIGINSdeploy/compose/.env.examplesets:The Tauri desktop app's webview origin is
tauri://localhost, so every REST call it makes is blocked by the browser.AddCommunityDialog→ "Join an existing community" fails with "Load failed" — WebKit's generic fetch error. No WebSocket is ever opened, so the relay logs show zero connection attempts, which makes it look like an auth or network problem rather than CORS.Reproduce against a relay deployed from the bundle:
crates/buzz-relay/src/config.rsalready documentstauri://localhostas the expected value, so this looks like the example file simply drifted from the code comment.Suggested fix — add the app origins to
.env.example:2. The bundle advertises NIP-43 but ships no pairing sidecar, so
/pair404sdeploy/charts/buzzdeploysbuzz-pair-relayas its own workload (templates/pairing-relay.yaml) and wiresBUZZ_PAIRING_RELAY_URL.deploy/composehas neither, but the main relay still advertises NIP-43 in its NIP-11 document.desktop/src-tauri/src/commands/pairing.rs::resolve_pairing_relay_urlkeys off exactly that: with NIP-43 advertised and nopairing_relay_urlpresent, it takes theLegacyPathbranch and dialswss://<relay>/pair. The main relay has no such route, so pairing fails 100% of the time on a compose deployment:In the UI this renders as the QR code flashing on screen and immediately disappearing, with
WebSocket connection failed: HTTP error: 404 Not Found.Suggested fix — ship the sidecar in the compose bundle, the same way the chart does. A working local override, for reference:
plus
BUZZ_PAIRING_RELAY_URL=wss://<domain>/pairin.envso NIP-11 advertises it explicitly rather than relying on the legacy path convention. With that in place/pairreturns101 Switching Protocolsand pairing completes normally.One footgun worth a comment in the example: because the image's
ENTRYPOINTis/usr/local/bin/buzz-relay, usingcommand:to select the pairing binary silently starts a second main relay instead. It then contends for the Postgres pool and crash-loops, and the proxy returns 502 — an error that looks nothing like the actual mistake.Happy to open a PR for either or both if that's useful.