Run it yourself
Muster is one compiled binary and a Postgres database. You run it on your own swarm. This page is the first run guide: from nothing to a running Muster in shadow mode. Hosts use the reserved .test domain, and every value here is a placeholder.
Where this stands
You build a small image around the release binary, and this page shows how. In shadow mode Muster records every order it would give and sends nothing, so you can watch it work before it acts.
The steps
1. Prerequisites
- A Postgres database and a role that owns it. CI uses version 17.
- A swarm, with a place for the stack file that lives outside the repository.
- A public HTTPS route to the webhook port and a private route to the operator port.
- A Linear workspace and a GitHub repository where you may add a webhook.
Get the binary
The release workflow uploads the build as a workflow artifact in a private repository. The artifact muster-web-linux-amd64 holds the program, its SBOM and SHA256SUMS. Fetch it with a token that may read the repository's actions, and check it.
gh run download RUN_ID -n muster-web-linux-amd64
sha256sum -c --ignore-missing SHA256SUMS
The program picks its role from its own file name. Copy it to the name muster and a plain serve runs both web and worker in one process.
2. Build the image
The binary is static and needs CA certificates to reach the tracker and forge. Use a static base with certificates and a non root user. The name below is a placeholder.
FROM registry.example.test/base/static:nonroot
COPY muster-web-VERSION-linux-amd64 /usr/local/bin/muster
USER nonroot
ENTRYPOINT ["/usr/local/bin/muster"]
chmod 0755 muster-web-VERSION-linux-amd64
docker build -t registry.example.test/muster:VERSION .
docker push registry.example.test/muster:VERSION
The entrypoint is the binary, so a stack command starts at the subcommand: ["serve", ...].
3. Secrets
Each secret is a file in -secrets-dir. A swarm secret is immutable, so put a version in its name and map it to the file name Muster reads.
| File | Holds | Needed |
|---|---|---|
database-url | the connection address | always |
operator-token | 16 characters or more; without it the ops pages are not mounted | yes |
github-webhook-secrets | one secret, or current,previous | for GitHub intake |
linear-webhook-secrets | the same form | for Linear intake |
tracker-token, forge-token | read by the tally, and by the executor | not for shadow mode with tally off |
services:
muster:
image: registry.example.test/muster:VERSION
command: ["serve", "-secrets-dir", "/run/secrets",
"-listen", ":8080", "-webhook-listen", ":8081",
"-ops-secure-cookies", "true"]
secrets:
- {source: database-url-v1, target: database-url}
- {source: operator-token-v1, target: operator-token}
- {source: github-webhook-secrets-v1, target: github-webhook-secrets}
- {source: linear-webhook-secrets-v1, target: linear-webhook-secrets}
To rotate one, create github-webhook-secrets-v2 from standard input, change source and deploy again. Remove the old secret when no service uses it.
4. Check, migrate, serve
muster check -secrets-dir /run/secrets --database
muster migrate -secrets-dir /run/secrets
muster serve -secrets-dir /run/secrets
check exits 0 when nothing required is missing. migrate prints the version it reached, and a second run says the schema is already current. On a swarm, run it as a one shot service before the stack starts, and run it again before each upgrade. After serve starts, /healthz answers 200, and /readyz answers 200 once the schema matches. Until settings are saved, every webhook answers 503 and names what is missing.
5. The first owner
Open /ops/setup on the operator listener. Give the operator token, an email, a name and a password. If the page is out of reach, use the command line, which reads the password from standard input.
muster users create-owner -email [email protected] -name Owner
6. Settings
A save is refused unless the whole settings pass validation, and 28 fields are required. They fall in these groups: connections, models and holds, budgets, roles and dispatch, guard, intake, labels and the runner. Turn Shadow runner (runner.shadow) on. No restart is needed, because the service rereads settings every five seconds. Keep the tally off until the tracker and forge tokens are in place.
workspace example-workspace
team example-team
tracker_url https://tracker.example.test/api
forge_url https://forge.example.test/api
budget.ticket_cents 250
budget.day_cents 2500
budget.run_cents 100
runner.shadow on
These are some of the neutral fixtures from the guide. Shadow mode never calls these addresses.
7. Webhooks
| Source | Address | Header read |
|---|---|---|
| GitHub | https://hooks.example.test/webhooks/github | X-Hub-Signature-256 |
| Linear | https://hooks.example.test/webhooks/linear | Linear-Signature |
Send only these two paths to the webhook port. Never publish the operator port, because /metrics shares it. The signing secret is the first value of the matching secret file. For GitHub the content type must be application/json. Linear shows its signing secret only when it creates the webhook, so create the sender first and then add the secret. A bad signature is refused before anything is recorded. Linear deliveries must carry a timestamp within one minute of the server clock.
Proof for each step
| Step | Proof |
|---|---|
| check, migrate, serve | check exits 0; /readyz answers 200 |
| first owner | sign in at /ops/login; /ops/ draws Now |
| settings | /ops/health lists the shadow loop as ok; webhooks stop answering 503 |
| webhooks | a test delivery is listed under Webhook deliveries on /ops/intake |
| shadow | label a ticket; /ops/dispatch shows its order with a rule |
The way back
- Stop reacting: set Shadow runner off. Events still reach the roll.
- Stop dispatch on the controls page, so a dispatch pass orders nothing.
- Remove the senders by deleting the two webhooks.
- Remove the service by scaling it to zero. The data stays in the database.
- Undo a bad settings save with Revert in the change history.
- Muster refuses a database newer than the binary. Restore from backup instead.
Configuration
All settings are parsed once, from flags and environment variables. A flag wins over the environment.
| Flag | Environment | Example | Notes |
|---|---|---|---|
-listen | MUSTER_LISTEN | 127.0.0.1:8080 | operator listener: health, metrics, ops pages |
-database-url | MUSTER_DATABASE_URL | postgres://db.example.test/muster | required, no default |
-log-level | MUSTER_LOG_LEVEL | info | debug, info, warn or error |
-shutdown-grace | MUSTER_SHUTDOWN_GRACE | 30s | time allowed for in-flight work at shutdown |
The binary in your hand
$ muster version
muster 0.58.2-0.20261113003756-419cd93ac087 (commit 419cd93, built 2026-11-13T00:37:56Z, go1.26.11)
$ muster help
usage: muster backfill|bosun|check|demo|helm|inspect|mcp|migrate|plan|roles|seats|sentry|serve|skiff|tally|ticket|users|verify|version
Or build from source with the Go toolchain: go build ./..., go vet ./..., go test ./... and go run ./cmd/muster inspect.