Skip to navigation

SSH from a controller

Add --target to facts, plan, apply, and journal to run them on a remote host over SSH. Compilation stays on your machine.

rivet facts --target app-1 > app-1.json
rivet compile ./site --facts app-1.json --out app-1.bundle
rivet plan app-1.bundle --target app-1 --sudo
rivet apply app-1.bundle --target app-1 --sudo
rivet journal --target app-1 --sudo

--target accepts anything OpenSSH does: user@host or a Host alias from your SSH config. The host must run Ubuntu 24.04 with systemd on x86_64 or ARM64. Applying requires root, so either log in as root (--target root@app-1) or add --sudo.

Connecting

Rivet runs your system ssh with strict host-key checking, BatchMode, and no terminal. Bastions, aliases, and keys from your SSH config and agent work as usual, but nothing can prompt: enroll host keys and set up key authentication first. --ssh-config FILE, --ssh-user USER, and --ssh-port PORT override settings for every connection Rivet makes.

--sudo runs /usr/bin/sudo -n /bin/sh -c ..., so the login user needs passwordless sudo for a shell. A sudo rule that allows only the Rivet binary isn’t enough. Running arbitrary catalogs as root is root-equivalent anyway, because catalogs can contain commands.

How the runner gets there

Rivet first looks for a compatible runner already on the host: on PATH, in /usr/local/bin or /usr/bin, or in Rivet’s runner cache. “Compatible” means it supports this bundle’s formats and the exact capabilities it needs; the Rivet versions don’t have to match.

If there isn’t one, Rivet uploads a runner to a private cache, verifying it before use:

  • Root uses /var/cache/rivet/runners/<digest>/rivet.
  • Other users use ~/.cache/rivet/runners/<digest>/rivet.

The two caches are separate. Collecting facts as an unprivileged user doesn’t install a runner for later --sudo runs. Rivet never overwrites an installation you manage, and root never executes a binary a user could have modified.

Use --installed-only to refuse uploads and require a runner that is already installed.

Linux artifacts from a Mac

A macOS build can’t run on Linux, and Rivet doesn’t cross-compile. Build Rivet on Ubuntu 24.04 for the host’s architecture and pass it with --runner-binary the first time:

rivet facts --target root@app-1 --runner-binary ./rivet-linux-aarch64 > app-1.json
rivet compile ./site --facts app-1.json --out app-1.bundle
rivet plan app-1.bundle --target root@app-1 --installed-only
rivet apply app-1.bundle --target root@app-1 --installed-only

Facts collection uploads the runner, and later commands reuse it from the cache. Use the same login identity throughout, since each identity has its own cache. --runner-binary uploads its file even if a compatible runner exists, which is also how you replace an older runner that lacks a capability you need.

Packaged releases will ship a runner-artifacts.json next to the controller binary, pinning the Linux runners that match it; --runner-manifest FILE selects one explicitly. No packaged releases are published yet, so source builds need --runner-binary or a runner installed on the host.

Paths and plugins

--facts-dir and --state-dir refer to paths on the remote host. --ssh-config, --runner-binary, and --runner-manifest refer to paths on your machine.

Plugins must be installed and approved on the host itself, normally in /etc/rivet/plugins.lock.json. A --plugin-lock on the controller applies only to the controller. Uploading a runner doesn’t install plugins, and a missing plugin is reported before any change.

Noop, output, and interruptions

Remote plan and apply --noop make no managed changes, but they can upload and cache a runner. Add --installed-only if that matters. Reports and exit codes are the same as for local runs.

If the connection drops after an apply starts, the outcome is unknown: the remote run may have finished, stopped partway, or still be running. Rivet doesn’t retry automatically. Check before applying again:

rivet journal --target app-1 --sudo

A fresh apply then converges normally, re-checking guards and delivering any pending refreshes. See interrupted SSH runs.

Limits

--timeout-seconds N limits a runner session to N seconds, from 1 to 86400 (the default). Bootstrap and probes have their own shorter limits. Bundles and runner executables can be up to 256 MiB, facts snapshots and captured command output up to 1 MiB, and retained command output up to 16 MiB per run; truncated output is marked. Bundles are sent over stdin and not kept on the host.