DEPLOYMENT
Deployment
How to run relay-edge on a laptop, a Linux host, or in Kubernetes — alongside relay-pubsub.
← Docs hub · See Configuration for all env vars
Choose your path¶
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Laptop │ │ Linux host │ │ Kubernetes │
│ go run │ │ systemd │ │ Helm pods │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────────┴───────────────────┘
│
optional: relay-pubsub
│
▼
Zyvor Relay
| Path | Best for | Command |
|---|---|---|
| Local | Development, UIs, unit smokes | go run ./cmd/relay-edge |
| systemd | Lab / edge appliance | ./scripts/deploy-remote.sh HOST |
| Kubernetes | Fleet-scale, co-located with pubsub | ./deploy/scripts/deploy-k8s-remote.sh HOST |
Local development¶
go test ./...
go run ./cmd/relay-edge
With gateway wiring:
export GATEWAY_BASE_URL=https://127.0.0.1:8081
export RELAY_AUTH_TOKEN=<jwt>
export RELAY_TLS_INSECURE=1
go run ./cmd/relay-edge
HTTPS mode (matches pod behaviour):
export EDGE_TLS=1
export EDGE_TLS_SAN=localhost,127.0.0.1
go run ./cmd/relay-edge
curl -k https://127.0.0.1:18086/healthz
Linux host (systemd or nohup)¶
# Optional: RELAY_AUTH_TOKEN or /tmp/lab-relay.jwt
./scripts/deploy-remote.sh <HOST> [USER]
Installs the binary under ~/.deployments/zyvor-relay-edge and writes relay-edge.env.
- systemd (default when
sudo -nworks): installsdeploy/systemd/relay-edge.serviceas/etc/systemd/system/relay-edge.serviceandenable --now. - nohup fallback: when systemd/passwordless sudo is unavailable (set
USE_SYSTEMD=0to force). - Auth is on by default. If
EDGE_API_TOKENisn't set in your shell, the script generates one and prints it once at the end — save it, it's not stored anywhere else.EDGE_REQUIRE_AUTHdefaults to1; deploying with it explicitly0requiresCONFIRM_INSECURE=1and prints a loud warning. - Keeps the previous binary as
bin/relay-edge.previouson the remote host for rollback. To revert:./scripts/rollback-remote.sh <HOST> [USER]. - Cleans up the plaintext JWT/token files it scp's to the remote
/tmpafter use, andchmod 600s the generatedrelay-edge.env.
| Variable | Deploy default |
|---|---|
GATEWAY_BASE_URL |
Peer pubsub URL (remote OK; omit when RELAY_EDGE_DIRECT=1) |
RELAY_BASE_URL |
Peer Relay URL (:8443 / :18080) — not laptop localhost |
RELAY_TLS_INSECURE |
1 (overridable — set RELAY_TLS_INSECURE=0 once Relay/gateway present trusted certs) |
EDGE_TLS |
1 (HTTPS; accept browser warning) |
EDGE_API_TOKEN |
Auto-generated if unset (see above) |
EDGE_REQUIRE_AUTH |
1 (needs CONFIRM_INSECURE=1 to explicitly disable) |
Manual unit install on an appliance:
sudo cp deploy/systemd/relay-edge.service /etc/systemd/system/
sudo mkdir -p /etc/relay-edge /var/lib/relay-edge
# edit /etc/relay-edge/relay-edge.env then:
sudo systemctl enable --now relay-edge
Direct Relay mode (edge → Relay, no pubsub):
RELAY_EDGE_DIRECT=1 RELAY_AUTH_TOKEN=<jwt> ./scripts/deploy-remote.sh <HOST> [USER]
Then run ./scripts/e2e-direct-stack.sh with config/lab-direct.env. Restore gateway mode by redeploying without RELAY_EDGE_DIRECT.
Verify:
EDGE=https://<HOST>:18086 ./scripts/smoke.sh
EDGE=https://<HOST>:18086 ./scripts/smoke-firewater.sh
EDGE=https://<HOST>:18086 ./scripts/smoke-remote-edge.sh
EDGE=https://<HOST>:18086 ./scripts/smoke-fleet.sh
Scheduled backups: deploy-remote.sh also copies scripts/backup-data.sh/restore-data.sh to the remote deploy dir. Install deploy/systemd/relay-edge-backup.service + relay-edge-backup.timer as a user systemd unit (see the comment header in the .service file) for a daily backup into ~/.deployments/zyvor-relay-edge/backups/.
Kubernetes¶
Requires sibling checkout of relay-pubsub at ../relay-pubsub.
On the cluster host: kubectl, helm, podman (or docker).
RELAY_AUTH_TOKEN="$(cat /tmp/lab-relay.jwt)" \
./deploy/scripts/deploy-k8s-remote.sh <HOST> [USER]
Creates:
| Release | Namespace | Port | TLS |
|---|---|---|---|
relay-pubsub |
relay-pubsub |
8080 | self-signed |
relay-edge |
relay-edge |
18086 | self-signed |
Verify on the cluster:
bash deploy/scripts/k8s-e2e.sh
Helm charts: deploy/helm/relay-edge/ · ../relay-pubsub/deploy/helm/relay-pubsub/
Default Helm image: ghcr.io/zyvorai/relay-edge (published on every v* release and on main via release-image). Lab script builds locally with podman (or BUILDER=docker) and imports into k3s — it does not require GHCR pull.
# Local image build (also what deploy-k8s-remote.sh does on the host)
BUILDER=podman ./deploy/scripts/deploy-k8s-remote.sh <HOST>
# Or pull a published image into your own cluster:
helm upgrade --install relay-edge deploy/helm/relay-edge \
--set image.repository=ghcr.io/zyvorai/relay-edge \
--set image.tag=v0.1.2
Optional Helm values: edge.enabledFamilies (EDGE_ENABLED_FAMILIES), edge.gatewayAuthTokenKey, edge.apiTokenKey (EDGE_API_TOKEN), edge.requireAuth (defaults false — explicitly set true + apiTokenKey for anything beyond a lab), tls.existingSecret, ingress.*, backup.enabled (renders a CronJob running backup-data.sh against the data PVC — see PRODUCTION.md § Backup and restore). Production starting point: values-production.yaml · checklist: PRODUCTION.md.
Backup / restore: ./scripts/backup-data.sh · ./scripts/restore-data.sh (manual, or scheduled — see above and PRODUCTION.md).
deploy-k8s-remote.sh tags images by commit SHA by default (IMAGE_TAG override available), not a mutable :latest, so helm rollback actually reverts to the prior image.
Configuration essentials¶
| Variable | Default | Purpose |
|---|---|---|
EDGE_HTTP_ADDR |
:18086 |
Listen |
EDGE_TLS |
1 |
1 = HTTPS with auto cert (set 0 for plain HTTP) |
EDGE_TLS_CERT / EDGE_TLS_KEY |
/var/lib/relay-edge/tls/*.pem |
Cert paths |
EDGE_TLS_SAN |
localhost,relay-edge |
SAN for generated cert |
EDGE_API_TOKEN |
— | Bearer for /v1/* |
EDGE_REQUIRE_AUTH |
0 (binary default; deploy-remote.sh defaults it to 1) |
Fail start without token |
EDGE_RATE_LIMIT_RPS / EDGE_RATE_LIMIT_BURST |
0 (disabled) / 20 |
Per-client-IP rate limiting; 0 RPS disables it |
EDGE_MAX_BODY_BYTES |
8388608 (8 MiB) |
POST/PUT/PATCH body cap (413 when exceeded) |
EDGE_LOG_FORMAT |
text |
text or json (structured logs for aggregators) |
EDGE_ENABLED_FAMILIES |
(all) | Optional subset: firewater, remote-edge, fleet |
EDGE_DATA_DIR |
./data |
JSON stores |
GATEWAY_BASE_URL |
Peer pubsub URL | Empty = direct; remote pubsub OK |
RELAY_BASE_URL |
Peer Relay URL | Direct path; remote Relay OK (:8443 / :18080) |
RELAY_AUTH_TOKEN |
— | JWT (sync with pubsub) |
RELAY_TLS_INSECURE |
1 |
Skip TLS verify outbound (startup warning logged when left at this default) |
FASAL_GCP_PROJECT |
fasal-onprem |
Gateway project id |
Lab reference¶
Any of the three projects can be remote. Relay, relay-pubsub, and relay-edge do not have to share a host. Wire each process to the reachable URL of its peer; run e2e from your workstation with BASE / GATEWAY / EDGE set to those same remote URLs.
| Who talks to whom | Use |
|---|---|
Your laptop → stack (e2e-stack.sh, curl, browser) |
Public / lab host IPs — never 127.0.0.1 (that is your laptop) |
edge → pubsub (GATEWAY_BASE_URL) |
Host where pubsub listens (remote OK) |
edge → Relay direct (RELAY_BASE_URL) |
Host where Relay listens (remote OK) |
pubsub → Relay (RELAY_BASE_URL) |
Host where Relay listens (remote OK) |
Relay → Action Gateway (RELAY_ACTION_TARGETS) |
Host where pubsub /v1/actions listens — 127.0.0.1 only if Relay and pubsub are on the same machine |
Example all-on-one labs (still use the host IP from your laptop):
| Host | Relay | pubsub | console | edge |
|---|---|---|---|---|
<ephemeral-ip> |
:8443 |
:8081 |
:8082 |
:18086 |
<ephemeral-ip> |
:18080 |
:8081 |
:8082 |
:18086 |
Env templates: config/lab-stack.env.example · config/lab-stack-175.env.example.
| Service | Port | Notes |
|---|---|---|
| relay-edge | 18086 | HTTPS on remote deploy (EDGE_TLS=1) |
| relay-pubsub | 8081 | systemd; 8080 in-cluster |
| relay-pubsub console | 8082 | Stored UI (proxies gateway) |
| Relay | 8443 or 18080 | Host process — check RELAY_PORT / RELAY_ADDR |
| Zynera Web UI | 30862 | Optional — sibling Zynera repo |
| Zynera API gateway | 30631 | Relay RELAY_FORGE_BASE_URL for Decision Records |
JWT: same token in edge env, pubsub env, and k8s secrets (wherever each runs). After every Relay restart, re-login (demo/demo) and re-sync.
Farm Act: Relay RELAY_ACTION_TARGETS must reach pubsub’s /v1/actions (remote host URL if pubsub is not local). Binary must honor RELAY_TLS_INSECURE=1 for self-signed HTTPS (relay#8bef494). Gateway cert SAN must include every name/IP Relay uses to call it. Helper: scripts/lab-wire-relay-act.sh (assumes co-located Act targets unless you edit them).
Zynera decisions (optional): configure RELAY_FORGE_* on Relay only — not on relay-edge.
Stack verification: from your workstation, ./scripts/e2e-stack.sh with remote BASE/GATEWAY/EDGE — see TEST_RESULTS.md.
Scripts reference¶
| Script | Purpose |
|---|---|
scripts/deploy-remote.sh |
Build + SSH deploy (systemd or nohup; RELAY_EDGE_DIRECT=1 for direct) |
scripts/rollback-remote.sh |
Restore the previous binary deploy-remote.sh kept |
scripts/lab-wire-relay-act.sh |
Wire Relay Act targets + TLS insecure on lab |
deploy/scripts/deploy-k8s-remote.sh |
Full k8s stack (BUILDER=podman default) |
deploy/scripts/k8s-e2e.sh |
Port-forward + smoke on cluster |
scripts/smoke*.sh |
Local farm / firewater / remote-edge / fleet smokes |
scripts/e2e-events-matrix.sh |
All families → Relay (gateway) |
scripts/e2e-direct-stack.sh |
Direct Relay probe + expanded matrix |
scripts/ci/check-coverage.sh |
CI coverage-regression gate (default threshold 55%) |
Makefile |
make vet test build smoke-all release-binaries qualify |
CI and releases¶
| Workflow | Trigger | What it does |
|---|---|---|
| CI | PR + push to main |
gofmt/go vet/go test -race -cover, coverage gate, golangci-lint, govulncheck, four family smokes vs mock Relay, make qualify, plus separate jobs for auth+HTTPS smoke, backup/restore round-trip, Helm lint/kubeconform, and a kind cluster deploy |
| CodeQL | PR + push to main, weekly |
Go static security analysis |
| Release | v* tag or Actions → Run workflow |
Multi-arch binaries + GitHub Release (cosign-signed) + GHCR image |
| release-image | Push to main (code paths) |
go vet/go test/golangci-lint gate, then ghcr.io/zyvorai/relay-edge:latest + sha-* |
| lab-e2e | Weekly + manual | Full E2E matrix against real lab hosts via Tailscale — scaffolded, needs secrets before it runs (see PRODUCTION.md) |
| Deploy docs | Push to main (docs paths) |
Builds and publishes the MkDocs site to GitHub Pages |
Latest: releases · image ghcr.io/zyvorai/relay-edge. Build requires Go 1.27+.