The daemon
moonwayd on its own — every flag, headless fleet watching, webhook alerts, and where the journal lives.
Everything Moonway computes happens in one process: moonwayd opens watch
streams, keeps the cache, runs the engines, writes the journal, and serves a
token-guarded API on 127.0.0.1. The desktop app manages a daemon for you —
but the binary stands alone, and running it yourself unlocks the shapes the
app doesn't need: a fleet watcher on a server, alerts into a webhook, custom
rules, Postgres.
Headless fleet watching
moonwayd --watch staging.example,prod-a.example --db sqlite:$HOME/.moonway/fleet.db --webhook https://ntfy.example/oncall
--watch starts watching the named kubeconfig contexts from boot — no UI,
no clicks. Paired with --db the journal records
around the clock, and --webhook turns critical
Triage findings into pushes. The Fleet page in
the app shows every watched cluster's health at a glance.
Webhook alerts
--webhook POSTs JSON on the same edge-triggered discipline the desktop
notifications use: once when a critical finding appears, once when it
resolves, silence while a flapping condition bounces inside the 30-minute
dedupe window.
{
"cluster": "prod-a.example",
"item": {
"id": "crash|payments/api-7f9c…",
"sev": "crit",
"cat": "crashloop",
"kind": "Pod",
"ns": "payments",
"name": "api-7f9c…",
"title": "CrashLoopBackOff",
"count": 4
},
"at": 1754700000,
"resolved": false
}ntfy, a Slack webhook, or twenty lines of your own HTTP handler all work — anything that accepts a POST.
Flags
| Flag | Default | What it does |
|---|---|---|
--port | 5171 | Port to listen on; 0 picks a random one |
--host | 127.0.0.1 | Bind address — 0.0.0.0 only in-cluster |
--token | $MOONWAY_TOKEN, else random | Auth token; a random one is printed at boot |
--token-file | — | Read the token from a file, creating it with a random value if missing |
--db | in-memory | Persistence: sqlite:<path> or postgres://…; empty means the journal dies with the process |
--retention-days | 14 | Journal retention; 0 disables pruning |
--watch | — | Comma-separated contexts to watch from boot (fleet mode) |
--webhook | — | POST critical alerts to this URL |
--rules | — | Directory of custom CEL rules (*.yaml) |
--watch-scope | all | Generic-resource watching: all (eager) or lazy (on-demand, idle-reaped) |
--in-cluster | off | Use the pod service account instead of a kubeconfig |
--ui | off | Serve the embedded web UI (implied by --in-cluster) |
--log | stderr | Write the log to a file, size-capped and rotated |
--log-max-mb | 16 | Size cap for --log, per generation; two generations kept |
Two flags deserve a second look. --watch-scope lazy defers watching
custom-resource types until something asks for them and reaps idle watches —
the right trade on clusters with hundreds of CRDs. And --db defaults to
memory when you run the daemon by hand: if you want the journal to outlive
the process — you do — say so with sqlite: or postgres://.
The token
Every request carries the token; without it the API answers nothing. Set it
explicitly, or let the daemon mint one — it prints MOONWAYD_TOKEN <value>
at boot. --token-file is how the
login-item daemon and the desktop
app agree on a credential without a channel: first writer creates it, both
read it.