Documentation
Everything you need to deploy, configure, and operate Relay — self-hosted uptime monitoring with a built-in public status page.
Quick Start
One-liner (no config file)
docker run -d \
--name relay \
--restart unless-stopped \
-p 8080:8080 \
-v relay-data:/data \
-e RELAY_SECRET=$(openssl rand -hex 16) \
-e RELAY_SITE_NAME="Acme Status" \
-e RELAY_SITE_URL="https://status.example.com" \
-e RELAY_ADMIN_PASS=yourpassword \
ghcr.io/rohzzn/relay
- Status page:
http://localhost:8080 - Admin dashboard:
http://localhost:8080/admin(login:admin/ your password)
Docker Compose + Caddy (auto-TLS, recommended)
git clone https://github.com/rohzzn/relay
cd relay
cp .env.example .env
# Edit .env — set RELAY_SECRET, RELAY_ADMIN_PASS, RELAY_SITE_NAME, RELAY_SITE_URL
# Edit Caddyfile — replace status.example.com with your domain
docker compose up -d
Caddy handles Let's Encrypt automatically. No certificate configuration needed.
Build from source
git clone https://github.com/rohzzn/relay
cd relay
go build -o relay ./cmd/relay
RELAY_SECRET=secret RELAY_ADMIN_PASS=admin ./relay
Requires Go 1.22+. No other dependencies.
Configuration
All settings are environment variables. Only two are required:
| Variable | Required | Default | Description |
|---|---|---|---|
RELAY_SECRET |
Yes | — | HMAC key for session cookies. Generate with openssl rand -hex 16 |
RELAY_ADMIN_PASS |
Yes | — | Admin dashboard password |
RELAY_ADMIN_USER |
admin |
Admin username | |
RELAY_SITE_NAME |
Status |
Displayed on the public status page | |
RELAY_SITE_URL |
http://localhost:8080 |
Full public URL (used in email links) | |
RELAY_LOGO_URL |
— | Optional logo image URL for the status page | |
RELAY_SMTP_HOST |
— | SMTP server hostname | |
RELAY_SMTP_PORT |
587 |
SMTP port | |
RELAY_SMTP_USER |
— | SMTP username | |
RELAY_SMTP_PASS |
— | SMTP password | |
RELAY_SMTP_FROM |
— | From address for outgoing emails | |
RELAY_DATA |
./data |
Directory for relay.db |
|
RELAY_PORT |
8080 |
HTTP listen port | |
RELAY_CHECK_CONCURRENCY |
20 |
Max concurrent check goroutines | |
RELAY_RETENTION_DAYS |
90 |
Days of check history to keep |
Monitor Types
| Type | Target | Example |
|---|---|---|
http |
URL | https://api.example.com/health |
tcp |
host:port |
db.internal:5432 |
tls |
hostname | api.example.com |
dns |
hostname | example.com |
heartbeat |
auto | cron jobs POST to Relay |
Heartbeat Monitors
A heartbeat monitor expects your cron job to POST to Relay on each run. If it stops, Relay opens an incident.
After creating a heartbeat monitor, the dashboard shows the exact endpoint:
POST https://status.example.com/ping/{monitor-id}
Example crontab entry:
*/5 * * * * /path/to/job && curl -sS -X POST https://status.example.com/ping/YOUR_MONITOR_ID
Alert Channels
Configure channels in Admin → Alert Channels:
| Type | Config |
|---|---|
| Webhook | Any URL — Relay POSTs JSON on down/up events |
| Slack | Slack incoming webhook URL |
| SMTP credentials + recipient address |
Alerts have a built-in 10-minute cooldown per monitor to prevent alert storms. Recovery ("back up") notifications always send immediately.
Architecture
relay/
├── cmd/relay/ Entry point, graceful shutdown, healthcheck subcommand
├── internal/
│ ├── config/ Environment-based configuration
│ ├── db/ SQLite (WAL mode), all queries — no ORM
│ ├── check/ HTTP · TCP · TLS · DNS · Heartbeat
│ ├── scheduler/ Per-monitor goroutines with semaphore concurrency pool
│ ├── state/ FSM: up/degraded/down, auto incident open/close
│ ├── alert/ Cooldown-aware channel dispatcher
│ ├── notify/ Slack, SMTP, and webhook adapters
│ └── server/ HTTP handlers, WebSocket hub, HMAC session auth
└── web/
├── templates/ html/template pages — embedded in the binary
└── static/ CSS + minimal JS — embedded in the binary
Why Go?
Single static binary, goroutine-per-monitor scheduler, ~15 MB Docker image. Uptime Kuma is 200 MB and people notice.
Why SQLite WAL?
Zero ops. Back up with cp relay.db relay.db.bak. Handles dozens of concurrent readers. No Postgres to run.
Why HTMX?
The live dashboard has real-time updates with zero client-side framework. No React, no build step, loads in <300ms on a Raspberry Pi.
Roadmap
- ✓ HTTP, TCP, TLS, DNS, Heartbeat monitors
- ✓ Live dashboard via WebSocket
- ✓ 90-day uptime bars
- ✓ Incident management with timeline updates
- ✓ Email subscriber list with confirmation
- ✓ Slack, webhook, and SMTP alert channels
- ✓ Custom 404/500 pages
- ✓ Docker healthcheck subcommand
- v2:
relay-probe— lightweight agent binary for multi-region monitoring - v2: Maintenance windows (suppress alerts during deployments)
- v2: Response time history graphs
- v2: REST API
- v3: On-call schedule with rotating recipients
- v3: SSO (OIDC)
Contributing
- Fork the repo
go run ./cmd/relay— starts with live template reloading- Templates are in
web/templates/, styles inweb/static/style.css - Open a PR — all contributions welcome
Code style: Standard gofmt. No ORM — queries live in internal/db/db.go as plain SQL. No external router — Go 1.22 stdlib mux only.
License
MIT — see LICENSE on GitHub.
Relay is not affiliated with Uptime Kuma or Statuspage.io.