---
title: Box care
summary: Keep your box healthy by checking it, upgrading it, backing it up and restoring it, reading its logs, moving it to another server, and removing it.
audience: users, operators
owner: integrator
status: draft
---

# Box care

Your box is the always-on server that runs Vyre. On a Linux server it is a Docker Compose stack in
`/srv/vyre` with the `vyre` container, and its data in Docker volumes. You look
after it from your Mac with `vyre box ...`, which runs each step over SSH so you never open a shell
on the server, or on the server itself with the host's `vyre` command. This page covers the care
tasks. What each folder and volume holds, and who can reach the box, is in
[The box and the Mac](../concepts/box-and-mac.md#folders-and-volumes).

## Check on it

::: tabs
::: tab On this Mac
```
vyre box       # the box's address and SSH target, and whether it answers from here
```

```output
  your box  https://alex.vyre.run · alex@192.0.2.10
  answering · 0.0.1
```
::: tab On a server
```
vyre status    # running or not, version, role, how many modules started
vyre modules   # each module, and the error of any that failed
```

```output
  vyred running · 0.0.1 · box · pid 7 · up 5312s
  17 modules running
```
:::

On the server, the host's `vyre` is a small shell script, `/usr/local/bin/vyre`. It runs every
command in the `vyre` container, except three it handles itself: `vyre up` starts the stack if it
is not running, waits up to a minute for Vyre, then prints the setup link or your address;
`vyre update` upgrades (below); `vyre logs` follows Vyre's output.

> [!SNAG] `vyre box` says "no box yet: vyre box add <user@host>"
> The `vyre box` commands work only for a box this Mac knows the SSH target of. If you installed
> the box with the curl installer on the server, run `vyre box add alex@192.0.2.10` once: it finds
> Vyre already there and carries on from where it stands.

> [!SNAG] `vyre box` says "not answering from here"
> This Mac cannot reach your box, or the box is down. Check the box with `vyre status` on the
> server, and run `vyre doctor` on the Mac for the path and the relay.

## Read its logs

On the box:

```
vyre logs      # follow Vyre's output (docker compose logs -f vyre)
```

Vyre also writes a log file per day, `~/.vyre/logs/YYYY-MM-DD.log`. On a Docker box that is
`/home/vyre/.vyre/logs/` inside the `vyre_vyre-home` volume.

## Cap what it spends

Vyre keeps one ledger of what sessions, agents and memory spend, per provider and per day (UTC).
Some figures are estimates, which the list marks, because they are tokens times a price rather
than a cost the provider reported. A daily cap per provider stops the spending without asking you
on every call.

```
vyre spend                      # today's spend per provider against its cap
vyre spend raise claude 20      # cap Claude at $20 a day
vyre spend raise claude +5      # add $5 to the cap
vyre spend raise all 50         # one cap over every provider together
vyre spend raise claude off     # no cap
```

At the cap, the thread that was spending is paused with one line that says what happened and the
command to raise the cap, and memory answers from facts and search until the next UTC day or until
you raise it. A provider with no cap has none. The Vyre app shows the same list under Settings, Spending limits,
with **Change cap** for each one.

## Upgrade

::: tabs
::: tab On this Mac
1. Run:

   ```
   vyre box update
   ```

   It runs `vyre update` on the server over SSH, then compares versions.

2. If the box is now newer than your Mac, it says so and prints the command that upgrades the
   Mac:

   ```output
     the box runs 0.0.2, newer than this Mac's 0.0.1: npm install -g https://vyre.run/box/vyre.tgz && vyre up
   ```
::: tab On a server
```
vyre update
```
:::

`vyre update` brings the box to the newest release. It asks GitHub Releases for `vyre-ai/vyre`:
`stable` (the default) is the newest release that is not a prerelease, and `beta` is the newest of
either. A stable box never takes a prerelease, such as `0.2.0-rc.1`: only a box you set to `beta`
would. Pick one with `vyre update --channel beta` or `VYRE_CHANNEL=beta`, or a release by version
with `--to 0.2.0`. While GitHub has no release yet, or when you set `VYRE_BOX_URL` yourself, it
uses the site instead (`https://vyre.run/box/` by default), as it always has.

To look without changing anything, run `vyre update --check` on the server. It says which version
an update would install and from which channel, and whether the server already runs it. It exits 0
when the server is current and 1 when a newer release waits. `vyre update --check --json` prints one
line, `{ "current", "channel", "latest", "update", "source" }`. It downloads nothing but the release
list, and writes nothing. When the server already runs the newest release, `vyre update` says "already
on" that version and stops: it does not restart the server. `vyre update --reinstall` puts the same
version on again anyway.

Vyre looks for a newer release once a day and keeps the answer, so Settings can say "Update available" and show what changed. On a server set up by the install line, Settings also has an **Update to 0.x** button. It does not run anything itself: it drops a one-line request into `/var/lib/vyre-update/request`, a folder that only Vyre can write to and that root owns along with every folder above it, and a small systemd unit on the host (`vyre-update.path`, owned by root) runs `vyre update` for it. That update never accepts `--allow-unsigned`, never goes back to an older version than the newest this server has had (a version the host keeps in a file only root writes), waits at least ten minutes between updates, backs up your data first, and puts the old version back on its own if the new one does not start. The card shows each step and the result. The command above always works too, and a server with no systemd shows only the command.

Turn on **Update automatically** in Settings (off by default) and Vyre asks for a new release by itself between 2 and 5 in the morning, once per version, the same way. The channel is the host's own: set `VYRE_CHANNEL=beta` in the server's `.env`, or leave it on stable. Set `auto` to `"off"` inside the `update` object in `config.json` to stop the daily look. On a Mac there is no update button: run `vyre update` (see Update Vyre on a Mac, below).

Updates are signed. Every file comes from the release and is checked against its `SHA256SUMS`
before anything on the box changes. The `SHA256SUMS` list itself is checked against its Ed25519
signature (`SHA256SUMS.sig`) from Vyre's release key, which is built into the `vyre` command. An
unsigned or badly signed release is refused, and `vyre update --allow-unsigned` installs it anyway
after a plain warning that nothing proves it came from Vyre. The same holds for `vyre update` on
a Mac. A box that pulls its image instead of building it also checks every Vyre image of the
release with cosign against the release workflow's identity, and refuses to pull one it cannot
check. Then, in order:

1. It backs up the database with `vyre backup`, into `/home/vyre/.vyre/backups/pre-<version>.tar.gz`
   in the container, with a copy in `/srv/vyre/backups/` that only you can read. It is sealed like
   any `vyre backup` file and holds the sealed vault, so treat it like the vault.
2. It tags the image that runs now as `vyre:prev`, and keeps the box files as they are in
   `/srv/vyre/box.prev/`.
3. It refreshes the box files (`compose.yml`, `compose.build.yml`, `vyre.env.example`, `Dockerfile`,
   `dockerignore`) from the release. `.env` and `vyre.env` are never touched.
4. When `/srv/vyre/.env` lists `compose.build.yml` in `COMPOSE_FILE` and `VYRE_SOURCE` is
   `/srv/vyre/src`, it swaps in the new `vyre.tgz` and keeps the old one as `/srv/vyre/src.prev`,
   then rebuilds the image with fresh base images. Otherwise it pulls the new image.
5. It recreates what changed and waits up to a minute for Vyre. A server that has not answered yet
   but is still working (its log moved in the last 20 seconds) is waited for, up to ten minutes in
   all, since setting up the Records types after a restart can take that long.

If Vyre does not come up in that time, or goes quiet without answering, the update undoes itself: the old source, the old image,
the old box files and the database from step 1 all go back, and `vyre update` exits 1 saying it
rolled back. Store migrations only go forward, which is why the database comes back from the
backup. That means anything written between the backup and the rollback (a chat, a note, a
setting) is lost, and the update says so as it rolls back. A slow start that is still working is
waited for, so a rollback should be rare; if one happens, check `vyre logs` before you try again.

The automatic updater also keeps a floor, the newest version this server has had, and refuses anything older. After an update that rolled back following its publication, the floor can sit above the version that runs; that blocks only older releases, never the forward update. `sudo vyre raise-floor` raises it to the version installed here after an update you ran yourself, and takes no version. Once an update has come up healthy, nothing restores the database on its own, so nothing
you write after that is lost.

After a healthy update, when the release carries an Android build, it brings the phone app along:
it checks the APK and `android.json` against `SHA256SUMS`, copies the APK into `/home/vyre/.vyre/releases/android/`,
then `android.json` last (the old one stays as `android.json.prev`), and runs
`vyre call releases.sign`. If signing refuses (`no_release` or `release_mismatch`), the update
says so and still succeeds. A release without an Android build leaves the folder alone. Last, it
replaces the `vyre` command itself with the release's copy, and prints what `vyre up` prints.

Your volumes carry over, and each module migrates its own data at start
([Specification](../architecture/spec.md#71-store-config-events-modules-daemon), Section 7.1).

To go back by hand:

```
vyre update --rollback
```

It puts the previous source, image, box files and phone app manifest back, and keeps the database
as it is now. Run it again to go forward. To put the database from before the update back too:

```
vyre update --rollback --restore-data
```

That drops everything written since the update, so it says what it would drop and asks you to
type `restore`. Off a terminal, pass `--yes` instead.

The image carries the Claude Agent SDK that Vyre's own sessions run on, with the Claude Code it
bundles, in `/opt/vyre-sessions-sdk` ([ADR 0030](../adr/0030-sessions.md)). The box never
downloads it at runtime. Its version is pinned as `VERSION` in `core/sessions/sdk-pin.js`: a bump there
rebuilds the image, and `vyre update` brings it in like any other change.

`vyre update` refreshes the box files and the wrapper. Running the installer again does too, and
it also leaves `.env` and `vyre.env` alone:

```
curl -fsSL https://vyre.run/install.sh | sh
```

## Update Vyre on a Mac

On a Mac, or any machine where you installed Vyre with npm, `vyre update` updates Vyre itself:

```
vyre update --check    # is a newer release out? exit 0 when current, 1 when one waits
vyre update            # show what changed, then install it
```

```output
  0.1.0 → 0.2.0 · stable

  0.2.0
    what 0.2.0 changed

  Update to 0.2.0 now? (y/N) y
  updated 0.1.0 → 0.2.0 · vyred answering
```

It reads the releases of `vyre-ai/vyre` on GitHub and shows the notes of every release between
the version you run and the new one. When you say yes it:

1. backs up your data into `~/.vyre/backups/pre-<version>/`;
2. downloads the release into `~/.vyre/releases/<version>/` and checks every file against the
   release's `SHA256SUMS`, so a damaged or wrong download stops it before anything changes;
3. installs it with `npm install -g`, restarts Vyre the way `vyre up` does after an upgrade, and
   waits for Vyre to report the new version.

If Vyre does not come back on the new version, `vyre update` puts the previous version back,
restores the backup, and says it rolled back. Once the new version has answered, it never touches
your data again on its own.

| Option | Does |
|---|---|
| `--channel stable` or `--channel beta` | which releases to follow; the default is `stable`, or `channel` inside the `update` object in `config.json` |
| `--to <version>` | a given release, when an update says to step through one first |
| `--yes` | installs without asking; needed when there is no terminal to ask on |
| `--rollback` | puts the previous release back and keeps your current data |
| `--rollback --restore-data` | also puts back the data from before the last update |
| `--json` | one line of JSON; `--check --json` prints `{ current, latest, channel }` |

`--restore-data` drops everything written since that backup, so it says the backup's date and
asks you to type `restore`. With `--json`, or with no terminal, it needs `--yes` instead.
`~/.vyre/releases` keeps the two newest releases and the one you run.

> [!SNAG] "this vyre runs from a checkout; update it with git"
> You run Vyre from a clone of the repository, not an npm install. Run `git pull`, then
> `vyre up`.

> [!SNAG] "0.3.0 updates only from 0.2.0 or newer"
> A release sometimes needs an older one in between. Run the command it names, for example
> `vyre update --to 0.2.0`, then `vyre update` again.

On a box, the host's `vyre update` (above) does the job; inside the box's container,
`vyre update` says so and stops.

## Run the installer by hand

`vyre box add` from the Mac runs the installer for you. On the server you can run it yourself:

```
curl -fsSL https://vyre.run/install.sh | sh
```

It is `scripts/install-box.sh`. It is safe to run again: every run converges on the same state.
Pass options after `sh -s --`, for example `curl -fsSL https://vyre.run/install.sh | sh -s -- --dry-run`.

| Option | Does |
|---|---|
| `--dry-run` | prints every change and makes none (read-only checks still run) |
| `--yes`, `-y` | answers yes to every question |
| `--from DIR` | uses the box files in a local checkout and builds the image from it |
| `--print-link` | ends with only two lines on stdout for a program to read, VYRE_LINK with the setup link and, when there is one, VYRE_SSH with the `ssh -L` line; everything else goes to stderr |
| `--uninstall` | stops the stack and removes `/usr/local/bin/vyre`; the volumes stay |
| `--purge` | with `--uninstall`: also deletes the volumes, after listing them and asking |

| Variable | Default | Does |
|---|---|---|
| `VYRE_DIR` | `/srv/vyre` | where the stack goes |
| `VYRE_BOX_URL` | `https://vyre.run/box/` | where the box files come from |
| `VYRE_IMAGE` | `ghcr.io/vyre-ai/vyre:latest` | the image to pull |
| `VYRE_BUILD=tgz` | | builds from `vyre.tgz` even when the image can be pulled |
| `VYRE_LINK_ONLY=1` | | the same as `--print-link` |
| `VYRE_NO_UP=1` | | installs everything but does not start it |

What it does, in order:

1. Checks this is Linux. On a Mac it prints a hint and stops.
2. Checks for Docker with Compose 2.24 or newer. If Docker is missing it asks before running
   `curl -fsSL https://get.docker.com | sh`; with no terminal to ask on, or a no, it prints the
   command and stops. An old Compose gets the same kind of message and a stop.
3. Picks how to get the image: it pulls `VYRE_IMAGE` when the registry has it, and otherwise
   builds from `vyre.tgz`.
4. Downloads `SHA256SUMS`, then `compose.yml`, `compose.build.yml`, `vyre.env.example`, the host
   wrapper and, for a build, `vyre.tgz`, and checks each against its line. A missing line or a
   different hash stops the install before anything is written. With `--from DIR` the files come
   from the checkout instead.
5. Creates `/srv/vyre`, owned by you (the account that ran `sudo`, if you did), writes the box
   files into it, and unpacks `vyre.tgz` into `/srv/vyre/src` for a build.
6. Writes `/srv/vyre/.env` (mode 0600) with `COMPOSE_PROJECT_NAME=vyre`, `COMPOSE_FILE`
   (plus `VYRE_SOURCE` for a build) and `DOCKER_GID`, the group that owns the Docker socket. It
   writes the file only if it is not there. The one change it makes to an existing `.env` is to
   add `DOCKER_GID` when the line is missing.
7. Installs the host wrapper to `/usr/local/bin/vyre`. If something else already has that name,
   it asks first.
8. Runs `vyre up`, or `vyre up --print-link` with `--print-link`.

sudo is used only for Docker's own install, for `/srv/vyre` when `/srv` is root's, and for
`/usr/local/bin/vyre`. If your account cannot reach Docker, the installer runs the stack through
sudo and says so; from then on the host's commands need `sudo vyre`.

> [!WHY] Why not just add me to the docker group?
> Membership of the `docker` group saves typing `sudo vyre`, and makes your account
> root-equivalent on that server: anyone who can talk to Docker can start a container that mounts
> `/`. The installer leaves that choice to you. `vyre box add` offers it in its plan, because
> later steps over SSH cannot type a sudo password.

> [!SNAG] "Docker is installed but not running."
> Start it with `sudo systemctl start docker`, then run the installer again.

> [!SNAG] "checksum mismatch for ..." or "SHA256SUMS is not a checksum list"
> Nothing was installed. The download was damaged, or `VYRE_BOX_URL` points somewhere that is
> not a Vyre release. Unset `VYRE_BOX_URL` and try again.

## Back up

There are two kinds of backup.

**Vyre's own data**, taken while Vyre runs, on the box:

```
vyre backup                  # vyre-backup-YYYY-MM-DD.vyre in /home/vyre, asks for a passphrase
```

It asks for a passphrase twice (12 characters or more) and seals the file with it: there is no
unencrypted backup, and the file opens only with that passphrase. It holds `config.json`, a
consistent copy of the store (taken with SQLite's `VACUUM INTO` while Vyre writes), `vault/`,
`watchers/`, `modules/`, `certs/`, `names/` and `data/` (your artifacts), the Drive of every Space on the box (your documents and each project's files, still sealed), plus your project files
(the box's `/work`) and your session transcripts. Add `--skip-projects` if your projects live in
git or Drive, and `--skip-transcripts` to leave the transcripts out. It leaves out `models/`,
`logs/`, the socket and the pid file, and your Claude, Codex and Grok sign-ins: sign in again after
a restore, or pass `--with-provider-logins` to carry them. An unfinished backup resumes when you run
the same command again. The file lands in `/home/vyre`, inside the `vyre_vyre-home` volume. Copy
it off the box:

```
cd /srv/vyre && docker compose cp vyre:/home/vyre/vyre-backup-2026-09-27.vyre .
```

**The Space's members, grants and sealed values** come back too, once you have given your recovery code a single time (the `spaces.bundle.enrol` tool, or ask your assistant to set up the Space bundle). From then on Vyre writes `space-bundles/<space>.vyb` into the home by itself, every hour, with no further asking, and every `vyre backup` carries the folder. Every Space on the box has its own bundle and its own owner's code: a team's Space is turned on by its owner (Settings, Backups), and until then that screen says plainly "Space Harbor isn't backed up: its owner hasn't turned on backups." A bundle holds the Space's id, its members and grants, a copy of its store (for a team's Space), and each sealed value (an SSN, a bank number) sealed under a key that only that owner's recovery code opens. None of the old box's own keys is in it, and nothing in it is readable without the code.

On a fresh box, restore the backup and the Space in one step:

```
vyre restore vyre-backup-2026-10-10.vyre --force --recovery-code <your code>
```

If the box holds Spaces with different owners, `vyre restore` asks for each owner's code in turn (Enter leaves one for later), brings back every Space it has a code for, and names the rest.

The Space keeps its id, so every link and record reference still works. Its sealed values are sealed again under the new box's own keys. Your members are back with their roles, and the log says the Space was restored and from what point. What a member sealed on their own device (their vault, their chats' keys) is carried as ciphertext, never re-keyed: it opens again when that person's device next connects to the restored Space. The old log itself is not carried, only where it ended.

**Everything**, Claude Code's sign-in and transcripts, and your projects in `/work`, from the Mac:

```
vyre box backup                         # vyre-box-backup-YYYY-MM-DD.tar.gz here
vyre box backup ~/Backups/box.tar.gz
```

The box is stopped while this runs: it stops the stack, copies the `vyre-home` and `vyre-work`
volumes into one file on your Mac (mode 0600), and starts the stack again, even
if the copy fails or you press Control-C. `--force` replaces an existing file.

Both files contain your sealed vault. Keep them somewhere only you can read. The `vyre backup`
file is already sealed with its passphrase; encrypt the `vyre box backup` file yourself
(`age -r <key> file`).

## Restore

A `vyre backup` file goes back with Vyre stopped. Vyre is the container's main process, so
restore runs in a one-off container:

```
cd /srv/vyre
docker compose cp vyre-backup-2026-09-27.vyre vyre:/home/vyre/
docker compose stop vyre
docker compose run --rm vyre vyre restore /home/vyre/vyre-backup-2026-09-27.vyre --force
vyre up
```

Restore asks for the backup's passphrase. Without `--force`, it refuses to replace a store the box already has. It also refuses an
archive with paths outside the known folders, or with links in it.

The backup carries the artifacts your agents made (`data/artifacts`: every version, a dashboard's
data, a deck's images, and the archive and 30-day undo state). A restore swaps them in by moving the
box's current artifacts folder aside first and deleting it only once the new one is in place, so a
failed restore keeps what was there. If the power fails between those two steps, a folder named
`.artifacts.old-<number>` is left under `data/`; it is safe to delete once you have checked that
your artifacts are there. Public links come back as they were when the backup was made: a link that
was on then is on again.

A `vyre box backup` file holds the two volumes as folders (`vyre-home/` and
`vyre-work/`). To put it on a server that has no Vyre volumes yet, install without starting,
create the volumes the way Compose would, unpack into them, then start:

```
curl -fsSL https://vyre.run/install.sh | VYRE_NO_UP=1 sh
for v in vyre-home vyre-work; do
  docker volume create --label run.vyre=1 --label com.docker.compose.project=vyre \
    --label com.docker.compose.volume=$v vyre_$v
done
docker run --rm -i -v vyre_vyre-home:/b/vyre-home -v vyre_vyre-work:/b/vyre-work \
  alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc tar xzf - -C /b < vyre-box-backup-2026-09-27.tar.gz
vyre up
```

Start the old server's stack again only after you have taken Vyre off it: two boxes with
one identity fight over it. To go from one live server to another, `vyre box move` (next) does all
of this for you.

## Move to another server

From the Mac:

```
vyre box move alex@192.0.2.20
```

It installs Vyre on the new server, stops the old stack, streams the two volumes from old to
new through your Mac, starts the new one, and waits for it to answer at the same name and address. Once the
new box answers, Vyre is taken off the old server, whose volumes stay until you delete them.

The new server must not already hold a box or Vyre volumes. If anything fails after the old stack
stops, the move stops the new one and starts the old one again, and says which.

## Agents' computers

Agents' computers run as their own containers through Vyre's Docker proxy. It runs on every Vyre
server from the install: the `vyre` command names the `computers` profile itself, so there is nothing to
add to `/srv/vyre/.env`.

What the proxy allows is in
[The box and the Mac](../concepts/box-and-mac.md#the-agents-computers).

## Remove it

A server holds no name of its own: the name belongs to your space. Removing the server does not free the name; take the server off the space in the Vyre app if you want it to stop serving the name.

::: tabs
::: tab On this Mac
```
vyre box remove           # stop the stack, remove /usr/local/bin/vyre, keep the volumes
vyre box remove --purge   # also delete the volumes, after the server asks
```

It lists what it will do and asks first. Afterwards this Mac forgets the box.
::: tab On a server
```
curl -fsSL https://vyre.run/install.sh | sh -s -- --uninstall
curl -fsSL https://vyre.run/install.sh | sh -s -- --uninstall --purge
```
:::

Both run `docker compose down` in `/srv/vyre` and remove `/usr/local/bin/vyre`. Without `--purge`,
the volumes and `/srv/vyre` stay, so a reinstall picks up where it left off. `--purge` lists every
volume labelled `run.vyre=1` in project `vyre` and, after you say yes, deletes them: the vault,
Claude's sign-in, the store and `/work`. `/srv/vyre` stays either way, with its `.env`; remove it
with `sudo rm -rf /srv/vyre`. The installer never removes Docker, and leaves the images
(`docker image rm` them if you like).

On the Mac, `vyre down && npm rm -g vyre` removes Vyre and leaves `~/.vyre`.

## A box without Docker

A Linux box installed from npm with systemd units is upgraded with
`sudo npm install -g https://vyre.run/box/vyre.tgz && vyre up`, and removed with
`sudo vyre uninstall --system && sudo npm rm -g vyre` (`--purge` also deletes the data). See
[Without Docker](../get-started/without-docker.md).

## Next

- [The box and the Mac](../concepts/box-and-mac.md): what runs where, the folders and volumes, and
  who can reach the box.
- [Troubleshooting](../get-started/troubleshooting.md), when something does not start.
- [CLI reference](../reference/cli.md#vyre-box), every `vyre box` form.

## What root runs, and what it reads

Updates that run as root (the automatic path from Settings and `sudo vyre update`) never read a file you or an agent on your account can write as configuration. Root starts compose from its own copies of the released compose.yml, in a folder only root can write, with a root-written env file and every file, project and folder named explicitly. It refuses an override file or a COMPOSE_* setting, takes how the box is built (pulled, built from the released source, or from your own checkout) from a record it made when the updater was installed, and checks every ghcr.io/vyre-ai image of a release with cosign before pulling. `sudo vyre up` and the other root commands use the same copies once the updater is installed. A host with no updater (no systemd) keeps the stack folder's files, as the installer laid them down.

One thing no script can fix: if your account may run `sudo` without a password, anything running as you can already become root, and none of this protects you from it. Keep `sudo` asking for a password on a box where agents run as your account.

Being in the docker group is equivalent to root on that host, so only the owner should be in it. The installer's own first `vyre up` runs as you, straight after it lays down the files it just verified; root's copies exist from the next step, when the updater is installed.
