Fleet
Fleet
Fleet: VMs
# Register an existing VM (e.g. the Oracle box)
baryovm vm add oracle --host <your-vm-ip> --user opc --key ~/.ssh/id_ed25519
baryovm vm list
baryovm vm ping oracle # SSH in, report host + Docker version
baryovm vm exec oracle -- uname -sr
baryovm vm exec oracle -- df -h /var/www
baryovm vm exec oracle -o json -- cat /etc/os-release
baryovm vm bootstrap oracle # install Docker if missing
baryovm vm runner oracle --repo acme/app # GitHub Actions self-hosted runner
baryovm vm runner remove oracle --repo acme/app
baryovm vm remove oracle # forget it (does not destroy the machine)
baryovm vm forget-key oracle # drop its recorded SSH host key
Host keys
The first connection to a machine records its SSH host key in
~/.baryovm/known_hosts and prints the fingerprint. Every connection after that
must match, and one that does not stops the command:
host key for 203.0.113.10:22 changed
stored SHA256:aWxF...
offered SHA256:9KpQ...
A rebuilt VM is the ordinary reason, and so is someone answering on that address
who should not be. BaryoVM cannot tell them apart, so it stops. If you rebuilt
the machine, baryovm vm forget-key <name-or-host> drops the old key and the
next connection learns the new one. There is no flag that turns the check off
for every host.
Host keys in CI
Trust on first use assumes there is a first use. A CI runner has no memory between runs, so every run is the first one, and learning a key on each run means trusting whatever answers on that address, every time, with a key that can dump your database.
Strict mode refuses to learn. An unknown host is an error:
export BARYOVM_STRICT_HOST_KEYS=1 # or --strict-host-keys
So a CI job records the key first, and fails rather than trusting a new one:
- name: Pin the host key
run: |
mkdir -p ~/.baryovm
ssh-keyscan -t ecdsa,ed25519 "$VM_HOST" >> ~/.baryovm/known_hosts
- name: Deploy
env:
BARYOVM_STRICT_HOST_KEYS: "1"
run: baryovm stack release myapp
Better still, commit the expected known_hosts or keep it in a secret, so the
pin is reviewed rather than fetched from the network on every run. ssh-keyscan
at deploy time trusts whatever answers; a committed file does not.
The flag beats the environment, so --strict-host-keys=false is how a human
turns it off for one command on a machine where the workflow sets it globally.
vm exec runs an arbitrary command on one named VM with the SSH key already in
fleet.json. Everything after -- is the remote command, so its own flags reach
the VM. Put sudo -n in the remote command when you need root; there is no
--sudo flag. A non-zero remote exit becomes a non-zero CLI exit. Under
-o json the envelope keeps stdout, stderr and exitCode as separate fields.
vm add connects once, over SSH, to detect the host's OS and records it in fleet.json;
give --os linux|windows and it does not connect at all. Any host with a POSIX shell
(Linux, macOS, FreeBSD) is recorded as linux. If the host cannot be reached (refused,
timed out) or does not answer the probe, vm add still saves it, with no OS, which
reads as Linux, and warns; run vm add again when the host is up, or pass --os windows.
A host key that does not match the one on record is not "unreachable": vm add fails
and saves nothing. Entries from before this existed are read as Linux, and an os that
is neither linux nor windows is refused by every command that uses it.
On a Windows VM (OpenSSH Server, administrator account) only vm exec works so far.
The words after -- run as one PowerShell command. Pass the command as one quoted
argument, so your own shell leaves its spaces, quotes and pipes alone:
baryovm vm exec win -- 'Get-ChildItem "C:\Program Files" | Select-Object -First 3'
The exit code, and errors as plain text, come back under either OpenSSH default shell
(cmd or powershell). vm add detects which one and records it as shell, so re-add a
VM if you change its default shell. With --os windows nothing is detected and the
shell is recorded as cmd; add --shell powershell when the host's default is
powershell.exe. --shell is only valid with --os windows. A script is sent encoded,
which makes it about three times longer, so a very long one is refused before connecting
(cmd.exe takes a command line of 8191 characters, powershell.exe 32767). Every other
command stops with a message naming it and the VM.
GitHub Actions runner
baryovm vm runner oracle --repo acme/app
baryovm vm runner oracle --repo acme/app --labels docker,gpu --memory-max 6G --cpu-quota 300%
baryovm vm runner remove oracle --repo acme/app
vm runner installs a self-hosted runner for one repo on a registered VM. It needs the gh
CLI logged in with admin rights on the repo, and Docker on the VM (vm bootstrap). It creates a
github-runner user in the docker group, unpacks the runner under
/opt/github-runner/<owner>__<repo> (lowercase), registers it, installs it as a systemd service and checks
that the service is active.
| Flag | Default | |
|---|---|---|
--repo |
required | owner/name |
--name |
<vm>-<repo> |
runner name; reruns use --replace, so one registration stays |
--labels |
none | extra labels on top of self-hosted, Linux and the arch |
--version |
latest release | pin a runner version such as 2.320.0 |
--memory-max |
4G |
systemd MemoryMax for the service |
--cpu-quota |
200% |
systemd CPUQuota for the service |
It is safe to rerun. A configured, running runner is reported as alreadyReady and nothing
changes. A stopped one is started, and one with no service gets the service installed. A rerun
after a crash continues from the step that failed. If you pass --memory-max or --cpu-quota
explicitly, they are applied even to a runner that is otherwise ready: the drop-in is rewritten
and the service restarted, and the result reports limitsApplied. Leave them out and a ready
runner is not touched.
- A repo whose visibility is not
privateis refused before BaryoVM connects to the VM, because a fork pull request could run its code there. That includes enterpriseinternalrepos, which every enterprise member can see and fork. - Also turn on "Require approval for all outside collaborators" for fork pull request workflows in the repo's Settings, Actions, General page.
- The runner tarball is checked against the sha256 digest GitHub publishes for the release asset before it is extracted. A release with no digest is refused.
- The memory and CPU limits cover the runner and what it runs directly. Containers a job starts
through Docker (Testcontainers,
docker run) run underdockerdand are not bounded by them. vm runner removestops and uninstalls the service, unregisters the runner and deletes its directory. If no remove token can be fetched, or GitHub rejects it, it removes the local runner anyway and says so, and you have to delete the entry on the repo's Settings, Actions, Runners page by hand. If that local removal fails too, the command fails and leaves the directory in place for another try.
Provision + deploy in one (untested, billable)
# Preview only, no AWS calls:
baryovm vm provision web1 --provider lightsail --key ~/.ssh/id_ed25519 --dry-run
# The one button: provision (if new) -> install Docker -> deploy
baryovm up web1 --provider lightsail --key ~/.ssh/id_ed25519 \
--image nginx:alpine --container site -p 80:80
# On an already-registered VM, up just bootstraps + deploys:
baryovm up oracle --image app:latest --container app -p 80:8080