> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lagerdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update

> Update Lager Box code from GitHub repository

Update Lager Box software on Lager Boxes with comprehensive progress tracking and automatic configuration.

## Syntax

```bash theme={null}
lager update [OPTIONS]
```

## Options

| Option | Description |
| - | - |
| `--box BOX` | Lager Box name or IP address |
| `--version VERSION` | Release tag, semver pin, branch, or full 40-character commit SHA to update to (default: this CLI's release tag). See [Version pinning](#version-pinning). |
| `--yes` | Skip confirmation prompt |
| `--check` | Dry run: report what will change without modifying the box |
| `--force` | Update even if the box reports it is already up to date, and force a clean rebuild (wipes the cached image and the cargo/npm volumes) |
| `--pull` | Pull the pre-built box image from GHCR for a release tag instead of building on the box (falls back to a local build on any miss) |
| `--no-pull` | Never pull a pre-built image; always build on the box |
| `--verbose` / `-v` | Show detailed output (default shows progress bar) |

<Note>
  `lager update` targets one box per invocation. To update several, loop over them
  in your shell — the `--all` flag and its multi-box loop were removed in v0.18.2.
</Note>

## Pre-built box images

Release tags publish a box container image to the GitHub Container Registry. If a
pull is enabled, the client does three things:

1. It resolves the tag to an immutable digest.
2. It pulls that digest, pinned to the box's own architecture.
3. It verifies that the image reports the version you asked for.

Any miss at any step falls back to the local build, which is the original
behavior. A miss can be a branch target rather than a release tag, an unpublished
tag, an unreachable registry, or a mismatched image.

<Note>
  For `lager update`, pulling is **off by default**. It changes how the most
  load-bearing command in the fleet obtains the code it runs, so it soaks on
  Lager's own boxes first. Pass `--pull` to opt a single run in. To opt in a whole
  shell, set `LAGER_BOX_IMAGE_PULL` to `1`, `true` or `yes` (in any letter case).
  Any other value leaves the pull off.

  This differs from [`lager install`](/source/reference/cli/install), which already
  uses a pre-built image for a release tag by default. `--no-pull` works the same
  way on both. It is the switch that routes a fleet around a bad published image,
  and it needs no CLI release.
</Note>

More rules for the update pull:

* `--force` never pulls. It always builds the image on the box.
* `--pull` together with `--no-pull` is a usage error.
* The pull runs before the update stops the container, so the box keeps serving
  during the download. A miss leaves the box on its previous version.
* The update records the image source in `/etc/lager/image-source`:
  `ghcr:<digest>` for a pulled image, or `local:<build-hash>` for a build.

## Version pinning

Since **lager 0.22.0**, a semver value passed to `--version` resolves to the
release **tag** `vX.Y.Z`. The leading `v` is optional (e.g. `0.26.0` or
`v0.26.0`), and the common pre-release suffixes (`-rc1`, `-beta2`, `-alpha`,
`-preview`) are accepted too. Release tags are the single source of truth for a
pinned version.

```bash theme={null}
# All three pin the same release tag (v0.26.0)
lager update --box my-lager-box --version v0.26.0 --yes
lager update --box my-lager-box --version 0.26.0 --yes
lager update --box my-lager-box --version v0.27.0-rc1 --yes
```

Since **lager 0.41.0**, a full 40-character commit SHA resolves to that exact
commit:

```bash theme={null}
lager update --box my-lager-box --version 5d84c68612384eed2854638c1e0941a4ff8b7893 --yes
```

Only the full 40 characters are accepted — a short hex prefix cannot be told
apart from a branch name. Use this when you need a target that does not move.
`--version main` is re-resolved against `origin/main` each time it runs, so it
can mean a different commit minutes later. A commit has no pre-built image,
because only release tags publish one. So a SHA builds on the box just as a
branch does. The commit must also be reachable from a branch or tag on the
remote.

Any other value (`main`, `staging`, or a feature branch name) resolves to
`origin/<name>` as before.

### Default version

With no `--version`, the update targets the release tag that matches the CLI.
For example, lager 0.52.0 updates a box to `v0.52.0`. To move a box to a newer
release, upgrade the CLI and then run `lager update`. To get the latest
development code, pass `--version main`. Earlier releases of the CLI used
`main` as the default.

A default update never rolls a box back. If the box is ahead of the CLI's
release tag, the update changes nothing and exits 0, also with `--check`. The
box can be on a newer release or on a branch. The message names the command to
move the box to the CLI's release. For a box on a branch, it also names the
command to update the box along that branch.

<Warning>
  Per-release **version branches** (bare `X.Y.Z` branches) are deprecated in favour
  of tags. A value like `0.26.0` now resolves to the tag `v0.26.0`, not a branch of
  the same name. See `RELEASE_PROCESS.md` in the repository.
</Warning>

## Usage

### Basic Update

```bash theme={null}
# Update to the release that matches this CLI
lager update --box my-lager-box --yes

# Update to the latest main branch
lager update --box my-lager-box --version main --yes

# Update to staging branch
lager update --box my-lager-box --version staging --yes

# Update with verbose output
lager update --box my-lager-box --yes --verbose

# Force fresh Docker build (use for major code changes)
lager update --box my-lager-box --force --yes
```

### Updating Several Boxes

There is no built-in multi-box update. Loop in your shell, and use `--check`
first if you want to see which boxes are actually behind:

```bash theme={null}
# Report what would change on each box, without modifying anything
for box in my-lager-box staging-box pi-box; do
  echo "== $box"
  lager update --box "$box" --check
done

# Then update them
for box in my-lager-box staging-box pi-box; do
  lager update --box "$box" --yes
done
```

<Note>
  A `--all` flag existed before v0.18.2 and was removed along with the multi-box
  loop it drove. A plain shell loop continues to the next box when one fails, so an
  unreachable box does not stop the run.
</Note>

### Check Mode

`--check` changes nothing on the box. It prints a preview:

```text theme={null}
Update preview
  Box:        my-lager-box (100.64.1.42)
  Current:    0.46.2
  Target:     main
  Code:       ...
  Deps:       ...
  Container:  ...
  Host CLI:   will upgrade (0.46.2 -> 0.47.0)
  Estimated:  ...
```

`Current:` comes from `/etc/lager/version`. The preview does not show the
deployed ref. Use `lager hello` for the ref.

| Exit code | Meaning |
| - | - |
| `0` | The box is in sync. Nothing to do |
| `1` | The update changes something, or the check failed |
| `2` | No SSH key is confirmed for the box, or git could not compare the box with the target |

Exit `1` does not always mean that the box is behind. The check also exits `1`
when it fails. Examples are an SSH timeout, a failed probe of the box state, and
a box directory that is not a git checkout. A failed `git fetch` also gives `1`,
and that includes a commit SHA that the box cannot fetch. A box locked by another
holder also gives `1`. Read the output before you treat `1` as "update needed".

***

## Update Process

The update command performs the following steps (shown in progress bar):

1. **SSH Check** - Confirm key-based SSH access to the Lager Box
2. **Inspect and Fetch** - Read the box state, then fetch the target version
3. **Pull Updates** - Check out the target version on the box
4. **Host Setup** - Check the udev rules, the modprobe blacklists, the sudoers files, the host packages (BlueZ) and the host CLI. A missing host package is installed without a password prompt. If that is not possible, the update prints the command to run on the box and continues
5. **Image Pull** - With `--pull` and a release tag, pull the pre-built image
6. **Container Stop and Build** - Stop the running container, then build the image on the box if the update did not pull one
7. **Directories** - Set up the directories that the container mounts, such as the custom binaries directory
8. **Version Update** - Record the version in `/etc/lager/version` and the deployed ref in `/etc/lager/ref`
9. **Container Start** - Start the new container
10. **Status Verification** - Wait for the services, then confirm that they answer
11. **J-Link** - Install J-Link if the box does not have it (a failure here is not fatal)
12. **Host CLI** - Reinstall the `lager` CLI in `~/.lager_venv` on the box host, because the code changed

A host CLI failure is not fatal. The update prints a warning, and the final
summary adds `Host CLI on the box host was NOT updated: ...`. The box host needs
python3 3.10 or newer, or the update skips this step with a warning. A box that
is already up to date still gets the host CLI if its copy is absent, broken or
older.

`lager update` does not change the host firewall.

***

## Version Tracking

The Lager Box tracks its current version in `/etc/lager/version`. The
`lager boxes` listing queries each configured box and shows its current version
in the `version` column:

```bash theme={null}
# List all boxes with their current versions
lager boxes
```

The box also records the deployed ref in `/etc/lager/ref`, as
`<ref>@<short-sha>` (for example `main@85c1b64`). If the update cannot read the
commit, the file holds the bare `<ref>`. `lager update` writes the file on every
successful run, including a run where the box is already up to date.
`lager install` writes it too.

The version number alone cannot tell a branch deploy from a release deploy, so
two commands show the ref:

* `lager hello` shows `(release)` for the release tag of the box version, and
  the full ref for any other ref. A ref that is not a release tag adds
  `-- not a release build`.
* `lager boxes` shows `<version> (<ref-name>)` for a ref that is not a release
  tag.

A box last deployed by a CLI older than 0.43.0 has no ref file.

***

## SSH Key Authentication

The update command checks for a working SSH key before it changes anything:

* **With a working key**: The update runs without password prompts.
* **Without a working key**: The update offers to set up the `lager_box` key. That setup asks for the box password once.
* **If the key setup fails**: The update asks whether to continue with password authentication.
* **With `--yes`**: The update accepts both prompts.
* **With `--check`**: The update does not set up a key. It exits `2` when no key works.

To set up the key as a separate step:

```bash theme={null}
lager ssh-setup --box my-lager-box
```

***

## Firewall

`lager update` does not install, change or run the host firewall. `lager install`
configures it. To run the firewall script again, run it on the box:

```bash theme={null}
ssh -t <user>@<box-ip> 'sudo /usr/local/lib/lager/secure_box_firewall.sh'
```

The script resets ufw before it writes its rules, so it removes rules that you
added by hand. On the default `lagernet` network, these rules do not filter the
ports that the container publishes. See
[`lager install`](/source/reference/cli/install) and the Security Model section
of [SECURITY.md](https://github.com/lagerdata/lager/blob/main/SECURITY.md#security-model).

***

## Examples

```bash theme={null}
# Standard update workflow
lager update --box my-lager-box --version main --yes

# Update several boxes
for box in my-lager-box staging-box; do lager update --box "$box" --yes; done

# Update several boxes to the staging branch
for box in my-lager-box staging-box; do lager update --box "$box" --version staging --yes; done

# Force fresh build on a specific box
lager update --box my-lager-box --force --yes

# Verbose update for troubleshooting
lager update --box my-lager-box --verbose
```

***

## Troubleshooting

### Update Fails at Git Pull

```bash theme={null}
# Check the remote URL (should be HTTPS, not SSH)
ssh lagerdata@<box-ip> 'cd ~/box && git remote get-url origin'

# If it shows git@github.com:..., switch to HTTPS:
ssh lagerdata@<box-ip> 'cd ~/box && git remote set-url origin https://github.com/lagerdata/lager.git'
```

### Container Build Fails

```bash theme={null}
# SSH in and check Docker status
ssh lagerdata@<box-ip>
docker ps -a
docker logs lager
```

### Firewall Issues

```bash theme={null}
# Check firewall status (sudo can prompt, so allocate a terminal)
ssh -t lagerdata@<box-ip> 'sudo ufw status verbose'

# Re-run firewall setup on the box
ssh -t lagerdata@<box-ip> 'sudo /usr/local/lib/lager/secure_box_firewall.sh'
```

***

## A gateway in front of the box

Some boxes have a gateway in front of lager. The gateway is another container on the `lagernet` Docker network. It publishes lager's host ports and forwards requests to the lager container. A control plane can run one.

* If such a container already publishes port 5000, 8080, 8765 or 9000, lager starts in `--no-publish` mode. Then lager is reachable only through the gateway. This mode stays for later starts.
* Before any container stops, the command checks that no other container holds a host port that lager publishes. If one does, the command stops with the port and the container named, and the box keeps its current lager container.
* If a container still runs on `lagernet` but `/etc/lager/control_plane.json` is gone, the command shows a warning. That gateway refuses every connection until you re-link the box from its control plane. Lager cannot restore the file.

## Notes

* Progress bar is disabled when `--verbose` is used
* Container builds use the BuildKit layer cache. `--force` removes the cached image and the cargo/npm volumes first
* Version defaults to this CLI's release tag if not specified
* Update automatically verifies container health after restart
* `--check` reports what will change without modifying the box, so it is the safe
  way to find out whether a box is behind
* `--force` updates even when the box reports it is already current, wiping the
  cached image and the cargo/npm volumes (useful after major code changes)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.