Nephtys now ships as a multi-architecture container image, exposes a coherent nephtys_-namespaced metrics surface, and comes with a provisioned Grafana operations dashboard that works from docker compose up with nothing to import.
docker run --rm ghcr.io/andreabozzo/nephtys:0.3.0 --versionImages are published for linux/amd64 and linux/arm64 as 0.3.0, 0.3, and latest.
⚠️ Breaking: metric names
Every Prometheus metric gained the nephtys_ prefix. Label sets, help text and histogram buckets are unchanged — this is a rename only, and the old names are not dual-emitted. Update scrape rules, alerts and dashboards:
| Old | New |
|---|---|
events_ingested_total |
nephtys_events_ingested_total |
events_dropped_by_pipeline_total |
nephtys_events_dropped_by_pipeline_total |
events_published_total |
nephtys_events_published_total |
bytes_ingested_total |
nephtys_bytes_ingested_total |
bytes_published_total |
nephtys_bytes_published_total |
event_processing_duration_seconds |
nephtys_event_processing_duration_seconds |
dedup_cache_size |
nephtys_dedup_cache_size |
dedup_cache_evictions_total |
nephtys_dedup_cache_evictions_total |
nephtys_stream_state |
unchanged |
Go runtime and process collector series (go_*, process_*, promhttp_*) keep their standard names. Pre-1.0 the compatibility promise does not extend to metric names, and this was the last cheap moment to fix the namespace before adopters pinned it.
Highlights
Distribution — multi-arch images on GHCR, built from a hardened Dockerfile: runs as nonroot (uid 65532), version stamped through a build arg so --version reports a release rather than a commit hash, and cross-compiled so the arm64 leg never runs the Go toolchain under emulation. Pull requests build the image in a job holding no registry credential.
Operations dashboard — docker compose up -d yields a Grafana with the Prometheus datasource and a Nephtys — Operations dashboard already provisioned. Per-stream state, ingest/publish event and byte rates, drops broken down by middleware, latency quantiles, and dedup cache saturation against configured capacity, scoped by Instance and Stream variables. The dashboard JSON is committed under deploy/grafana/ and reviewed as source. An optional nephtys compose service behind a profile runs the published image inside the stack.
Stream health — one-hot nephtys_stream_state{stream_id,state} plus additive health and last_message_at fields on GET /v1/streams. New nephtys_dedup_cache_capacity gauge makes dedup saturation computable from metrics alone.
WebSocket on_connect_send — frames sent verbatim after every handshake including reconnects, unlocking sources that require a subscribe or auth frame.
Notable fixes
- Binary payloads were being corrupted by the pipeline. Dedup hashed the empty JSON payload, giving every binary event the same hash and dropping all but the first as a duplicate; batch discarded binary data and its content type entirely.
- Batch silently discarded buffered events when a stream was stopped or its pipeline hot-swapped. It now drains before exiting.
NEPHTYS_LOG_LEVELwas a documented no-op — loaded into config but never applied, sodebugdid nothing.- Grafana never started in the compose stack: recent releases refuse to boot the rendering service while
renderer_tokenis at its default, so the container exited during boot. - Compose NATS was perpetually
unhealthy— the distroless image has nowgetfor its own healthcheck. - The Wikimedia SSE example published nothing — it filtered on payload
typevalues, butfiltermatches the envelope type the connector sets, which for SSE is theevent:frame name. - Unregistered streams leaked metrics — all per-stream series now go away with the stream.
Evaluation
The peer-reviewed comparison against Node-RED 5.0.1 (IEEE UIC 2026 short paper, accepted) is summarized in docs/benchmarks/: 19.5 ± 0.1 MB vs 128.5 ± 0.4 MB tool RSS on a Raspberry Pi 5 at identical filtering results and matching event-sequence hashes. Wall power was indistinguishable between the two systems — the board's idle floor dominates at 40 events/s — so the footprint buys memory headroom for co-located workloads, not lower energy.
Full changelog: v0.2.0...v0.3.0