On this page
Onboarding
This path takes a new operator from no Stado state to an accepted canonical registry and then to normal fleet operation. If you already run Stado, first setup can adopt that existing registry instead of making you recreate hosts and fleets. Local mode needs no cloud account, provider CLI, or Wisent production credential.
Install an exact release
Set the immutable release identity, run the verified installer, and expose its binary directory:
export STADO_API_URL=<your-control-origin>
export STADO_RELEASE_VERSION=<exact-version>
./install-stado.sh
export PATH="$HOME/.stado/bin:$PATH"
Expected result: the installer prints the platform and $HOME/.stado/bin/stado as its last two lines. It reads the signed release's release.json and release.tar.gz, and refuses a manifest identity mismatch, an invalid source revision or digest, an archive whose size or SHA-256 differs from the manifest, or an archive without exactly one stado member, before installing anything.
Running stado without arguments opens the first-run path and records only its local journey progress. Import is optional; skipping it leaves an empty usable Stado setup.
Adopt an existing registry
Use the reusable CLI action directly, or select the same action from the first-run journey:
stado registry import /path/to/registry.json --json
# first-run equivalent
stado onboarding --import-registry /path/to/registry.json --json
The accepted input is a complete Stado registry-v2 JSON document — the same exact schema stado registry validate and the canonical store use, not a guessed CSV or generic inventory format. Stado validates the whole source and its fleet-to-target references before it opens the destination. It then additively merges named targets, fleets, coordinators, and placement_profiles, recursively adds destination-missing object fields, and preserves every declaration already in canonical storage. The source file is read-only and is not copied into application storage.
A byte-for-byte or semantically identical repeat reports the source declarations as unchanged. A name already present with different content, or any differing scalar or array without a domain identity, returns conflict, exits 75, and performs no write. Invalid JSON, an unsupported schema, an invalid target, or an invalid fleet relationship returns rejected, exits 1, and performs no write. Storage failures also exit 1. --json emits stado.registry-import-receipt.v1: the accepted generation plus exact imported and unchanged host and fleet names, sections, conflicts, and rejections. It never prints registry credential contents.
An accepted import persists only the merged canonical registry generation and local onboarding evidence containing the source digest and accepted generation. It reads no host, starts no workload, installs nothing, and does not change live fleet state. The same importer remains available later in Stado Desktop Settings → Existing registry.
Create the minimum local configuration
stado config init
stado config validate
stado doctor --fix-hints
Expected results:
config initprints the path to the new$HOME/.stado/config.json;- the file selects only the local compute provider, local primary and backup storage, no remote deployment binding, and loopback dashboard binding;
config validateprintsconfig ok (<path>);doctor --fix-hintsreports actionable missing workload dependencies without requiring cloud credentials.
config init never overwrites an existing file. For a legacy config without a schema marker, use stado config migrate; it preserves the exact prior document as a timestamped sibling before adding the current schema. Production Wisent policy, verifier allowlists, and cloud credentials belong in explicit deployment profiles under deploy/, not in first-run config.
Start the control plane
An API listener bounds every request by the memory the host can give it,
unless the deployment declares its own
dashboard.request_limits document;
a malformed declaration refuses startup before a socket is accepted.
stado config set dashboard.request_limits "$REQUEST_LIMITS_JSON" # only to declare your own bounds
stado config validate
On a machine that belongs to no fleet registry, run the scheduler, the worker and the loopback API listener as one foreground process:
stado serve --standalone --worker --kind local --poll-seconds 15 \
--control-plane local --control-plane-interval-seconds 15 \
--api --bind <LOOPBACK> --port <PORT>
A machine the registry declares installs the same process as its one unit,
com.wisent.stado, which comes back after a reboot:
stado bootstrap --local --target <this host's registry name>
--dry-run prints the unit it would write and enables nothing. Do not expose
the listener publicly without the documented deployment authorization
boundary: it serves no page, and the fleet is inspected with stado overview
or Stado Desktop.
Submit and inspect one job
From another terminal using the same config:
stado submit "printf 'hello from Stado\n'"
stado status <job-id>
stado results <job-id> ./stado-result
Expected result: status progresses from queued or running to completed. The downloaded command output contains hello from Stado; the result manifest records the artifact size and SHA-256. A failed job remains inspectable and may still publish logs and partial artifacts.
Onboard another machine
A machine enters the fleet by one of four methods, invite, adopt, join or declare, each available from the CLI and from Stado Desktop, and the fleet always dials the machine, so the machine only ever receives a public key: Onboard another machine explains choosing a method and each one.
Reading the fleet from Stado Desktop
Once a machine reports, three screens answer from the desktop the questions that would otherwise need an SSH session. Each one runs the stado commands documented in the CLI reference and shows their answers; build the app with swift build --package-path desktop/StadoDesktop. The screenshots are live reads of the Wisent fleet.

Hosts — read why a host is claiming no work: the blockers its own agent publishes, its free space against the enforced watermark and the age of its capacity report (stado host gates), and preview-first disk reclamation (stado space reclaim).

Services — read what the host is actually running against what it declares, including the binary each process is really executing (stado release version show), and the product processes no unit owns (stado service list --unowned).

Releases — read why a rollout is stuck: the verdict and blockers (stado release doctor), the candidate's own stderr off the host (stado release logs), and the quarantined digests with the desired one first, which is where a digest is cleared with a typed reason (stado release quarantine list|clear).
Failure guidance
config file already exists: validate or migrate it; do not overwrite it implicitly.ERROR config schema_version ...: runstado config migrateonly for a trusted legacy config. Future schema versions require a compatible Stado release.- storage unreachable: stop submission, preserve the source, and follow
stado storage backup,stado storage verify, or the outage recovery procedure. Unreachable storage is never treated as an empty queue. - job remains queued: confirm an eligible worker is running, the queue is not paused, capacity fits, and the workload deadline has not expired.
- worker rejects or fails a job: install the requested shell/runtime/driver on that worker or change the workload. Stado does not silently supply workload dependencies.
- API authorization error: each route family (object, release, machine, service, host-health) resolves its own scoped bearer; check the grant for the route being called. Do not bypass or disable the authorization boundary.
- immutable release collision or digest failure: stop. Publish a new version; never overwrite the existing coordinate.
Uninstall and local reset
Stado removes what it installed through the product surface it installed:
stado product remove stado --surface cli
It removes only the paths the installation owns. Local config
($HOME/.stado/config.json), local storage and local backup stay; remove them
yourself after stopping every writer and copying whatever must survive.
Source: this website