DEPLOYMENT
Deploying relay-pubsub
How to run the gateway on a laptop, Linux host, or in Kubernetes — alone or with relay-edge.
Deployment reference (template)¶
Track your own instances in ops notes — do not commit live hostnames or IPs to git.
| Host | User | HTTP | gRPC | Backend | Notes |
|---|---|---|---|---|---|
<host> |
<user> |
8081 (HTTPS) |
50061 (gRPCS) |
relay-events |
systemd on host. Self-signed cert at /var/lib/relay-pubsub/tls/. RELAY_BASE_URL=https://127.0.0.1:8443, RELAY_TLS_INSECURE=1. Pre-registers 40 topic names (farm + edge + remote-edge + fleet catalogs). JWT must match relay-edge. |
<host> |
<user> |
8082 (HTTPS) |
— | n/a | Product console + demo/tests — Ops console + product demo |
<host> |
<user> |
8080 (HTTPS, in-cluster) |
50051 (gRPCS) |
relay-events |
k8s pod in namespace relay-pubsub. Deployed via relay-edge deploy/scripts/deploy-k8s-remote.sh. Reaches host Relay at https://<host>:8443. |
Manage systemd gateway:
ssh <user>@<host> systemctl status relay-pubsub
ssh <user>@<host> cat /etc/relay-pubsub/relay-pubsub.env
BASE=https://<host>:8081 bash scripts/smoke-relay-events.sh
make deploy-remote-quick H=<host> U=<user>
Manage k8s pods:
ssh <user>@<host> kubectl -n relay-pubsub get pods
ssh <user>@<host> bash ~/.deployments/k8s-edge-stack/relay-edge/deploy/scripts/k8s-e2e.sh
Ops console + product demo¶
ui/ is a Zyvor-branded product site (hero, Generate catalogs, live demo, in-browser tests, ops console) deployed via scripts/deploy-console-remote.sh — own systemd unit, port 8082, self-signed HTTPS.
Same-origin fetch: Node ui/server.mjs proxies /v1, /admin, /healthz, /readyz, /metrics to the gateway so the browser only trusts one cert.
# Optional 3rd arg = SSH password (sshpass).
bash scripts/deploy-console-remote.sh <host> <user> [password] \
--gateway https://127.0.0.1:8081 \
--project projects/demo
curl -k https://<host>:8082/ # product site
curl -k https://<host>:8082/healthz # proxied gateway health
# https://<host>:8082/#generate # seed catalogs
# https://<host>:8082/#demo # live publish → pull → ack
# https://<host>:8082/#tests # browser smoke + conformance
# https://<host>:8082/#console # Incoming / Outgoing / Stored / Logs
Console panes:
| Pane | Purpose |
|---|---|
| Incoming | Publish into topics |
| Outgoing | Pull / live / push endpoint / ack |
| Stored | Inventory (counts, backlog, peek without consuming) |
| Configure | Create / delete topics & subscriptions |
| Logs | Live gateway process log tail |
Include the host IP in gateway PUBSUB_TLS_SAN (and regenerate certs) so server-side clients and the console proxy can reach the gateway by name/IP.
Full install + test docs: INSTALL.md · TESTING.md.
Deployment targets¶
- Bare Linux host via systemd —
scripts/deploy-remote.sh - Kubernetes pods — Helm chart, k3s, or relay-edge stack deploy
- relay-pubsub + relay-edge stack — full integration path
Also: Installation (GHCR pull, Docker run) and Testing (acceptance checklist).
Functional proof for all paths: real publish (and pull for memory backend) via scripts/smoke.sh or scripts/smoke-relay-events.sh — not /healthz alone.
1. Bare Linux host via systemd¶
Prerequisites¶
- Local:
ssh,rsync; for--build-local: Linux + Rust or docker (cross-build via Dockerfile). - Target: Debian/Ubuntu or RHEL-family, SSH, passwordless
sudorecommended.
Deploy¶
bash scripts/deploy-remote.sh <host> <user> --build-local --quick
make deploy-remote-quick H=<host> U=<user>
Profiles: full remote build (default), --quick, --build-local, --verify-only, --uninstall [--purge], --fleet. See scripts/deploy-remote.sh --help.
Configuration highlights¶
| Variable | Default | Purpose |
|---|---|---|
PUBSUB_TLS_SAN |
localhost,relay-pubsub |
Include 127.0.0.1 if Relay calls https://127.0.0.1:8081/v1/actions |
RELAY_BACKEND |
memory |
Use relay-events for real Relay integration |
RELAY_BASE_URL |
http://relay:9090 |
e.g. https://127.0.0.1:8443 on lab |
RELAY_TLS_INSECURE |
unset | Set 1 when Relay uses self-signed TLS |
RELAY_AUTH_TOKEN |
empty | JWT — must match relay-edge |
Example lab /etc/relay-pubsub/relay-pubsub.env:
PUBSUB_HTTP_ADDR=0.0.0.0:8081
PUBSUB_GRPC_ADDR=0.0.0.0:50061
PUBSUB_TLS_SAN=localhost,127.0.0.1,<host>,relay-pubsub
RELAY_BACKEND=relay-events
RELAY_BASE_URL=https://127.0.0.1:8443
RELAY_TLS_INSECURE=1
RELAY_AUTH_TOKEN=<jwt>
Regenerate TLS cert after changing PUBSUB_TLS_SAN (delete /var/lib/relay-pubsub/tls/*.pem, restart).
Full variable list: deploy/systemd/relay-pubsub.env.example.
Uninstall¶
make deploy-remote-uninstall H=<host> U=<user>
bash scripts/deploy-remote.sh <host> <user> --uninstall [--purge]
Verify (systemd)¶
BASE=https://<host>:8081 bash scripts/smoke.sh
BASE=https://<host>:8081 bash scripts/conformance-smoke.sh
BASE=https://<host>:8081 bash scripts/smoke-relay-events.sh
BASE=https://<host>:8443 GATEWAY=https://<host>:8081 \
bash scripts/fasal-catalog-smoke.sh
curl -k https://<host>:8081/admin/v1/inventory?project=projects/demo
curl -k https://<host>:8081/admin/v1/logs?limit=20
See Testing for the full acceptance checklist.
2. Kubernetes pods¶
Helm chart (deploy/helm/relay-pubsub/)¶
Defaults updated for production-style deploy:
relay.backend=relay-events- TLS
emptyDirvolume at/var/lib/relay-pubsub/tls/ PUBSUB_TLS_SANviatls.sanvalue- Probes:
scheme: HTTPS - Secret
relay-auth-tokenwhen backend ishttporrelay-events
Local k3s (memory backend, no Relay)¶
bash deploy/scripts/deploy-k3s.sh
bash deploy/scripts/ci-k3s-e2e.sh
relay-pubsub + relay-edge stack¶
Both pods use built-in self-signed HTTPS. Deployed together from the relay-edge repo:
# In relay-edge repo (sibling relay-pubsub required):
RELAY_AUTH_TOKEN="$(cat /tmp/lab-relay.jwt)" \
./deploy/scripts/deploy-k8s-remote.sh <HOST> [USER]
| Release | Namespace | Service |
|---|---|---|
relay-pubsub |
relay-pubsub |
:8080 HTTPS |
relay-edge |
relay-edge |
:18086 HTTPS |
On-cluster verify:
bash deploy/scripts/k8s-e2e.sh
Uses scripts/smoke-relay-events.sh + relay-edge firewater smoke + remote-edge/fleet publish path.
Note: Pods reach host Relay via https://<node-ip>:8443 (set from SSH host in deploy script). host.k3s.internal is not reliable on all clusters — deploy script uses the explicit host IP.
Manual Helm (single gateway)¶
kubectl create namespace relay-pubsub
kubectl -n relay-pubsub create secret generic relay-pubsub-secrets \
--from-literal=relay-auth-token="$RELAY_AUTH_TOKEN"
helm upgrade --install relay-pubsub deploy/helm/relay-pubsub \
-n relay-pubsub \
--set relay.backend=relay-events \
--set relay.baseUrl=https://<relay-host>:8443 \
--set relay.tlsInsecure=1 \
--set tls.san="localhost,relay-pubsub,relay-pubsub.relay-pubsub.svc.cluster.local"
Integration with relay-edge¶
| Component | Role |
|---|---|
| relay-edge | Stamps farm/simulator events, publishes to gateway |
| relay-pubsub | Maps Pub/Sub topics → POST /v1/events; receives actions at /v1/actions |
| Relay | Policies, notify, ack, act, verify |
Event matrix (all four families): relay-edge docs/EVENT_MATRIX.md and scripts/e2e-events-matrix.sh.
Full stack verification (2026-08-28, all PASS): relay-edge docs/TEST_RESULTS.md — health probe, event matrix, Forge Decision Record path. Use config/lab-stack.env from relay-edge; set GATEWAY to this gateway's HTTPS URL.
Relay action targets (for farm Act evidence):
RELAY_ACTION_TARGETS=farm-controller=https://127.0.0.1:8081/v1/actions,\
firewater-controller=https://127.0.0.1:8081/v1/actions,\
remote-edge-controller=https://127.0.0.1:8081/v1/actions,\
fleet-controller=https://127.0.0.1:8081/v1/actions
Relay needs RELAY_TLS_INSECURE=1 (or trust gateway cert) when action URL is HTTPS with self-signed cert.
Troubleshooting¶
503 on publish (relay-events): Check RELAY_AUTH_TOKEN, RELAY_BASE_URL reachability from gateway process/pod, and Relay health.
401 from Relay: JWT mismatch between edge, pubsub secret, and Relay RELAY_JWT_SECRET.
Farm Act fails (mockact_* or circuit breaker): Relay action target wrong, gateway TLS not trusted, or PUBSUB_TLS_SAN missing 127.0.0.1.
TLS cert errors: Use curl -k; ensure PUBSUB_TLS_SAN includes all names/IPs clients validate against before first cert generation.
Known limitation: multi-replica action queue¶
/healthz is liveness-only. /readyz probes Relay when RELAY_BACKEND=relay-events.
Local subscribe/ACK/action-queue state persists to disk (PUBSUB_DATA_DIR) but is still per-replica. Keep replicaCount: 1 (or sticky sessions) until Relay owns durable cursors.