Development operation
Current runtime
The source expects Go 1.25 with toolchain 1.25.12. Running the relay starts
one HTTP server, listening at the configured server.listen_addr (default
:8080), and opens the DuckDB file at the configured storage.db_path
(default db/relay.db).
go run ./cmd/relay
A Dockerfile and docker-compose.yml exist, and CI publishes versioned
images to ghcr.io/nostrfi/relay on every build (GitVersion-driven semver,
sha-<shortsha>, and latest). A documented, exercised TLS-terminating
reverse-proxy overlay (docker-compose.tls.yml, using
Caddy) and a rollback recipe against those
published tags also exist — see the source repository's README.md
"Deployment" section. TLS is not implemented in the application itself; that
boundary is deliberate, not a gap — see the
trust and data page.
Configuration
config.yaml contains the NIP-11 relay information object (name,
description, operator public key, contact, advertised software URL,
supported NIPs, and advertised relay limitations), plus operational
settings: resource limits, NIP-42 auth binding, WebSocket origin policy,
retention, server listener and shutdown timeout, and the database path. See
the source repository's README.md "Configuration" section for the full
reference and current defaults — every setting is configurable, with
defaults matching what used to be hardcoded.
If the file is absent, code defaults supply basic identity and NIP values.
LOG_LEVEL accepts debug, info, warn, or error; the default is
info.
config.yaml are included in the NIP-11 response but
are not applied to connections or messages unless the corresponding
enforcement exists. Enforcement now covers message size, subscription and
filter counts, content length, tag count, timestamp bounds, proof-of-work,
and auth requirements — operators should still confirm against the source
README.md rather than assume a field is active.Storage lifecycle
Schema changes are applied by a versioned migration runner, recorded in a
schema_migrations table; a migration failure aborts startup rather than
serving traffic against a partially-migrated schema. A background worker
deletes events whose NIP-40 expiration has passed (and their tag rows) on a
configurable interval, then runs DuckDB's checkpoint and vacuum. -backup
and -restore flags on the relay binary export and import the database
using DuckDB's native tooling, with a row-count manifest verified at restore
time.
Deletion and replacement on the existing delete/replace code paths still do not remove associated tag rows, which can create orphaned index data over time — this remains open and is tracked separately from the storage-lifecycle work above.
Logging and observability
Logs are structured JSON on standard output. /healthz reflects process
health only; /readyz verifies the database is reachable. /metrics
exposes Prometheus-format metrics: connections, messages by type, rejections
by reason, subscriptions, query latency, events stored, and save failures,
with cardinality-bounded labels. Negentropy reconciliation logging is at
Debug and no longer includes the raw payload value. There is still no
distributed tracing.
Production-readiness checklist
- Define public, private, or product-specific relay policy and ownership.
- Make listener, public relay URL, data path, and shutdown settings explicit.
- Put authenticated TLS termination in front of the service (via a documented, exercised reverse-proxy overlay — the relay itself still never terminates TLS directly).
- Enforce message, connection, subscription, filter, event, and time limits.
- Add connection and publication rate limits plus resource backpressure.
- Restrict and test the WebSocket origin policy.
- Add liveness, readiness, metrics, dashboards, and alerting (an initial, explicitly provisional dashboard/alert set — a formal service-level objective has not been approved yet). Distributed tracing remains open.
- Establish schema migrations, backup, restore, retention, and purge jobs.
- Define moderation, abuse response, legal requests, and operator access.
- Load-test expected event volume, database growth, queries, and fan-out.
- Threat-model untrusted JSON, large events, connection floods, and metadata.
- Add release packaging, dependency scanning, and a deployment runbook
(CI runs
govulncheckon every build; a finding fails the build).
Previous
Current limitationsNext
Protocol support