PRODUCTION
Production runbook
User / site deployment of relay-edge (edge companion for Zyvor Relay).
← Docs hub · Deployment · Security
Network topology¶
┌─────────────────────────────────────────┐
Operators / UI │ Ingress / LB (real TLS, optional WAF) │
Scripts / agents │ edge.example.com │
└──────────────────┬──────────────────────┘
│ HTTPS
▼
┌─────────────────────────────────────────┐
│ relay-edge :18086 │
│ API auth: EDGE_API_TOKEN │
│ PVC: EDGE_DATA_DIR (JSON + optional TLS)│
└───────────┬───────────────┬─────────────┘
│ │
gateway mode │ │ direct mode
(recommended) │ │ GATEWAY_BASE_URL=
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ relay-pubsub │ │ Zyvor Relay │
│ :8080/:8081 │──▶│ :8443 / :18080 │
└──────────────────┘ └────────┬─────────┘
│ Act (optional)
▼
┌──────────────────┐
│ farm / plant │
│ controllers │
└──────────────────┘
Ports
| Service | Typical port | Notes |
|---|---|---|
| relay-edge | 18086 | HTTPS default (EDGE_TLS=1) |
| relay-pubsub | 8080/8081 | Publish gateway |
| Relay | 8443 / 18080 | Accept / Notify / Act (site-specific listen port) |
| Zynera (optional) | — | Not called by edge; Relay may gate Act |
Keep edge off the public internet unless Ingress + EDGE_API_TOKEN + real TLS are in place.
Current maturity (2026-09-15)¶
| Claim | Status |
|---|---|
| Software matrix + CI (auth TLS, helm, kind-edge, backup) | green — image/chart v0.1.2 |
| Lab host auth + TLS + backup | signed — ops-checklist.md |
| Defaults without token | still lab-open — set EDGE_API_TOKEN + EDGE_REQUIRE_AUTH=1 |
| HA / multi-replica | not supported (replicaCount: 1) |
Verdict: relay-edge is production-ready as a single-replica stamped feeder when items 1–4 below are applied (lab host has auth+TLS after redeploy).
Production checklist¶
Engineering preview defaults: treat unset token as lab-open. Do not expose beyond a trusted network until items 1–4 are done.
- API auth — set
EDGE_API_TOKEN(andEDGE_REQUIRE_AUTH=1so the process refuses to start without it)../scripts/deploy-remote.shdoes this by default since it auto-generates a token when none is supplied. The Helm chart's own default is not auth-required (edge.requireAuth: false) — for k8s deploys, explicitly setedge.requireAuth: trueandedge.apiTokenKey(pointing at a key inedge.existingSecret) the same wayvalues-production.yamlshould. - TLS — terminate at Ingress with a trusted cert, or mount a real cert via Helm
tls.existingSecret(keyscert.pem/key.pem). Do not rely on auto-generated self-signed certs for users. - Tokens — use real Relay / gateway JWTs (
RELAY_AUTH_TOKEN,GATEWAY_AUTH_TOKEN). SetRELAY_TLS_INSECURE=0once CAs trust Relay and pubsub. Left at its default (1, unset), relay-edge logs a startup warning that outbound TLS verification is disabled — this only fully closes once Relay/pubsub themselves present trusted certs, which is outside relay-edge's own scope to fix alone. - Persistence — PVC for
EDGE_DATA_DIR; schedulescripts/backup-data.sh— see Backup and restore below for the systemd timer and Helmbackup.enabledCronJob paths. - Body limits — default
EDGE_MAX_BODY_BYTES=8388608(aligned with ingressproxy-body-size: 8m); lower if the site only posts small JSON events. - Simulators / families — set
EDGE_ENABLED_FAMILIESto only what the site needs (farm,firewater,remote-edge,fleet), or leave empty for all. When the list is set, farm must be included explicitly or farm APIs stay off. - Replicas — keep
replicaCount: 1. JSON file stores are not multi-writer safe. - Metrics — scrape
GET /metrics(Prometheus text;prometheus/client_golang-backed, includes a request-duration histogram and Go runtime/process series alongsiderelay_edge_*). Path is public; protect via NetworkPolicy (networkPolicy.enabled, on in production values). A starting dashboard and alert rules are checked in atdeploy/observability/grafana-dashboard.jsonanddeploy/observability/alerts.yaml— neither is wired into Helm/CI automatically, since that depends on how you run Prometheus/Grafana. - Admin —
/v1/admin/*requires the same API token when auth is enabled. Enter the token in/ui→ Configure →edge_api_token(browser localStorage). JWTs are not written toruntime-config.json. - Qualify —
make qualifygreen; see QUALIFICATION.md. Graceful SIGTERM drain is 10s — setterminationGracePeriodSeconds≥ 15.
Lab E2E freshness: .github/workflows/lab-e2e.yml scaffolds a weekly re-run of the full gateway/direct matrix against the real lab hosts and, on success, opens a PR bumping TEST_RESULTS.md's "Last re-run" date — but it needs manual setup before it can pass: a Tailscale tailnet reaching the lab hosts, plus TS_OAUTH_CLIENT_ID/TS_OAUTH_CLIENT_SECRET/LAB_212_RELAY_AUTH_TOKEN/LAB_175_RELAY_AUTH_TOKEN repo secrets. Until that's wired up, the date stays a manual process.
When not to run relay-edge in production: if you only need Relay Accept from real devices/protocols — skip the simulators. Run this service when you intentionally need stamped topology + synthetic or operator-driven events.
Helm starting point: deploy/helm/relay-edge/values-production.yaml.
kubectl create secret generic relay-edge-secrets \
--from-literal=relay-auth-token="$RELAY_JWT" \
--from-literal=gateway-auth-token="$GW_JWT" \
--from-literal=edge-api-token="$EDGE_API_TOKEN"
helm upgrade --install relay-edge ./deploy/helm/relay-edge \
-f ./deploy/helm/relay-edge/values-production.yaml \
--set image.tag=v0.1.2
Auth¶
| Path | When EDGE_API_TOKEN set |
|---|---|
/healthz, /readyz, /version, /metrics |
Public |
/ui/* |
Public (static UI) |
/v1/* including /v1/admin/* |
Authorization: Bearer <token> or X-Edge-Token: <token> |
Unset EDGE_API_TOKEN = lab-open mode (warning logged). Scripts: EDGE_API_TOKEN=… EDGE=https://… ./scripts/smoke.sh (pass the header manually until scripts gain built-in support, or unset for lab).
Backup and restore¶
EDGE_DATA_DIR=/var/lib/relay-edge/data ./scripts/backup-data.sh /backups/edge-$(date +%F).tgz
# stop edge, then:
EDGE_DATA_DIR=/var/lib/relay-edge/data ./scripts/restore-data.sh /backups/edge-….tgz
# start edge
Backup includes seasons/sites/zones/devices/contacts, runtime-config.json, and TLS files under the data dir if present.
Scheduling it — this is a manual script; nothing runs it automatically unless you wire one of these:
- systemd targets (
deploy-remote.sh):deploy-remote.shcopiesscripts/backup-data.sh/restore-data.shto the remote deploy dir. Installdeploy/systemd/relay-edge-backup.service+relay-edge-backup.timeras a user unit (see the comment header in the.servicefile) for a daily backup into~/.deployments/zyvor-relay-edge/backups/. - Kubernetes / Helm: set
backup.enabled: true(plus optionallybackup.schedule,backup.size) to render aCronJob(deploy/helm/relay-edge/templates/backup-cronjob.yaml) that runsbackup-data.shagainst the existing data PVC into a dedicated<release>-backupsPVC, onbackup.schedule(default daily at 03:00).
Full stack install (high level)¶
- Deploy Relay with production TLS and auth.
- Deploy relay-pubsub pointed at Relay; sync gateway token.
- Deploy relay-edge with gateway URL + tokens +
EDGE_API_TOKEN. GET /readyz→ 200;POST /v1/admin/probewith Bearer token.- Optional: wire Relay Act → controllers (
lab-wire-relay-act.shis lab-oriented; adapt for production targets). - Run user smoke against the Ingress URL with
EDGE_API_TOKEN.
See INTEGRATION.md and RELAY.md.
Compatibility¶
| Component | Notes |
|---|---|
| Go build | Go 1.22+ (see go.mod) |
| Kubernetes | 1.25+ recommended; Helm 3 |
| Relay | Zyvor Relay with /v1/events Accept API |
| relay-pubsub | Matching project topic publish path |
| Browsers | Modern Chromium/Safari/Firefox for /ui |
| Prometheus | Scrapes /metrics (OpenMetrics-ish text 0.0.4) |
Image: ghcr.io/zyvorai/relay-edge:<tag> — pin release tags, not latest.
Support¶
| Channel | Use for |
|---|---|
| GitHub Issues | Bugs, feature requests (non-security) |
| security@zyvor.ai | Vulnerabilities — see SECURITY.md |
| https://zyvor.dev | Product / vendor |
Include: edge version (GET /version), publish path (gateway vs direct), auth_required / tls from /healthz, and whether Ingress or raw :18086 is used.
Explicitly out of scope (today)¶
- Multi-replica HA writers on shared JSON stores
- Built-in ACME / Let’s Encrypt inside the binary
- IdP / OIDC (use Bearer edge token or put SSO at the Ingress)
- Full k8s e2e in CI without lab cluster secrets