Download and start the Relayer
One compose file, one command. The images are public — no registry login needed.
Create a folder and save the compose file:
mkdir -p ~/xns-relayer && cd ~/xns-relayer
Save this as docker-compose.yml inside that folder:
services:
relayer:
container_name: xns-relayer
image: releases.scpri.me/xns-relayer:release-latest
pull_policy: always
restart: unless-stopped
privileged: true
command: >
sh -c '
if [ "${RELAYER_AUDIT_MODE:-loki}" = "loki" ]; then
export S3GW_AUDIT_NDJSON_PATH=/var/log/relayer-audit/audit.ndjson;
fi;
exec /xns/xns.sh
'
ports:
- "${UI_PORT:-8888}:8888" # web UI
- "${S3_PORT:-9000}:9000" # S3 API (HTTP)
- "${S3_TLS_PORT:-9443}:9443" # S3 API (HTTPS, once you install a cert)
environment:
- NODE_ENV=production
- XNS_RELEASE_CHANNEL=release
- UI_PORT=${UI_PORT:-8888} # same expression as the ports line above — keeps the logged port honest
- S3GW_AUDIT_WEBHOOK_URL= # webhook audit mode only — see .env below
- S3GW_AUDIT_WEBHOOK_SECRET=
volumes:
- relayer_data:/relayer
- ./data:/relayer-legacy-check:ro # upgrade safety: old bind-mount data is detected, never overwritten
- /mnt:/mnt:rslave
- audit_logs:/var/log/relayer-audit
- relayer_logs:/relayer/log # app logs on their own volume, so the log shipper never sees config/secrets
networks:
default:
ipv4_address: 172.28.0.10 # pinned address — Grafana dashboard sign-on trusts exactly this IP
# Audit-log stack (default on) — ships your S3 audit trail to an on-box Loki.
# Controlled by RELAYER_AUDIT_MODE in the .env below; set it to off or webhook to skip.
loki:
image: releases.scpri.me/relayer-loki:release-latest
pull_policy: always
restart: unless-stopped
profiles: ["loki"]
command:
- -config.file=/etc/loki/loki.yml
volumes:
- loki_data:/loki
alloy:
image: releases.scpri.me/relayer-alloy:release-latest
pull_policy: always
restart: unless-stopped
profiles: ["loki"]
command:
- run
- --server.http.listen-addr=0.0.0.0:12345
- /etc/alloy/config.alloy
environment:
- S3GW_AUDIT_LOKI_PUSH_URL= # optional: point audit logs at your own Loki/SIEM
depends_on:
- loki
volumes:
- audit_logs:/var/log/relayer-audit:ro
- relayer_logs:/relayer/log:ro # application logs → searchable in Grafana, read-only
audit-logrotate:
image: releases.scpri.me/relayer-audit-logrotate:release-latest
pull_policy: always
restart: unless-stopped
profiles: ["loki"]
volumes:
- audit_logs:/var/log/relayer-audit
prometheus:
container_name: prometheus
image: releases.scpri.me/relayer-prometheus:release-latest
pull_policy: always
restart: unless-stopped
# Alert thresholds — leave empty for sensible defaults; tune in the .env, then recreate this service
environment:
- ALERT_DISK_FREE_BYTES=${ALERT_DISK_FREE_BYTES:-}
- ALERT_DISK_FREE_FOR=${ALERT_DISK_FREE_FOR:-}
- ALERT_S3_UNREACHABLE_FOR=${ALERT_S3_UNREACHABLE_FOR:-}
- ALERT_S3_ERROR_RATE=${ALERT_S3_ERROR_RATE:-}
- ALERT_S3_ERROR_RATE_FOR=${ALERT_S3_ERROR_RATE_FOR:-}
- ALERT_S3_P95_LATENCY_SECONDS=${ALERT_S3_P95_LATENCY_SECONDS:-}
- ALERT_S3_P95_LATENCY_FOR=${ALERT_S3_P95_LATENCY_FOR:-}
- ALERT_HOSTIO_DOWN_FOR=${ALERT_HOSTIO_DOWN_FOR:-}
- ALERT_GATEWAY_DOWN_FOR=${ALERT_GATEWAY_DOWN_FOR:-}
- ALERT_CRASH_LOOP_FOR=${ALERT_CRASH_LOOP_FOR:-}
- ALERT_CACHE_PRESSURE_RATIO=${ALERT_CACHE_PRESSURE_RATIO:-}
- ALERT_CACHE_PRESSURE_FOR=${ALERT_CACHE_PRESSURE_FOR:-}
- ALERT_BRIDGE_STALE_SECONDS=${ALERT_BRIDGE_STALE_SECONDS:-}
- ALERT_BRIDGE_STALE_FOR=${ALERT_BRIDGE_STALE_FOR:-}
- ALERT_TARGET_DOWN_FOR=${ALERT_TARGET_DOWN_FOR:-}
- ALERT_AUDIT_SIEM_DEAD_FOR=${ALERT_AUDIT_SIEM_DEAD_FOR:-}
volumes:
- prometheus_data:/prometheus
alertmanager:
# Alert delivery — routes firing alerts to your webhook/email; works out of the box, receivers optional
container_name: alertmanager
image: releases.scpri.me/relayer-alertmanager:release-latest
pull_policy: always
restart: unless-stopped
environment:
- ALERTMANAGER_WEBHOOK_URL=${ALERTMANAGER_WEBHOOK_URL:-}
- ALERTMANAGER_GROUP_WAIT=${ALERTMANAGER_GROUP_WAIT:-}
- ALERTMANAGER_REPEAT_INTERVAL=${ALERTMANAGER_REPEAT_INTERVAL:-}
- ALERTMANAGER_SMTP_SMARTHOST=${ALERTMANAGER_SMTP_SMARTHOST:-}
- ALERTMANAGER_SMTP_FROM=${ALERTMANAGER_SMTP_FROM:-}
- ALERTMANAGER_SMTP_TO=${ALERTMANAGER_SMTP_TO:-}
- ALERTMANAGER_SMTP_AUTH_USERNAME=${ALERTMANAGER_SMTP_AUTH_USERNAME:-}
- ALERTMANAGER_SMTP_AUTH_PASSWORD=${ALERTMANAGER_SMTP_AUTH_PASSWORD:-}
- WATCHDOG_HEARTBEAT_URL=${WATCHDOG_HEARTBEAT_URL:-}
- WATCHDOG_REPEAT_INTERVAL=${WATCHDOG_REPEAT_INTERVAL:-}
volumes:
- alertmanager_data:/alertmanager # silences survive restarts
grafana:
image: releases.scpri.me/relayer-grafana:release-latest
pull_policy: always
restart: unless-stopped
environment:
# Dashboards sign you in through the Relayer UI — no separate Grafana login, no anonymous access
- GF_AUTH_ANONYMOUS_ENABLED=false
- GF_AUTH_PROXY_ENABLED=true
- GF_AUTH_PROXY_HEADER_NAME=X-WEBAUTH-USER
- GF_AUTH_PROXY_HEADER_PROPERTY=username
- GF_AUTH_PROXY_AUTO_SIGN_UP=true
- GF_AUTH_PROXY_WHITELIST=172.28.0.10
- GF_USERS_AUTO_ASSIGN_ORG_ROLE=Admin
- GF_SERVER_SERVE_FROM_SUB_PATH=true
- GF_SERVER_ROOT_URL=%(protocol)s://%(domain)s/grafana
volumes:
- grafana_data:/var/lib/grafana
node-exporter:
image: prom/node-exporter:v1.11.1
restart: unless-stopped
command:
- --path.rootfs=/host
volumes:
- /:/host:ro,rslave
volumes:
relayer_data:
relayer_logs:
prometheus_data:
grafana_data:
alertmanager_data:
audit_logs:
loki_data:
networks:
# fixed private subnet so the Relayer keeps the pinned address Grafana trusts
default:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/24
gateway: 172.28.0.1
And save this as .env in the same folder — it switches the audit-log stack (leave it as-is for the default):
# loki (default) = on-box audit-log stack · webhook = POST to your SIEM · off = no audit trail
#RELAYER_AUDIT_MODE=off
#S3GW_AUDIT_WEBHOOK_URL=
#S3GW_AUDIT_WEBHOOK_SECRET=
# Never edit the line below and never set COMPOSE_PROFILES yourself.
COMPOSE_PROFILES=${RELAYER_AUDIT_MODE:-loki}
First run pulls the images (~a few hundred MB). Give it a minute or two, then open http://localhost:8888 in your browser.
✓ Web UI loads? Your Relayer is running. If ports are taken, see Troubleshooting below.