Skip to main content
Deploy the Lager Box software, Docker container, and supporting tools onto a new or existing box.

Syntax

Options

Either --box or --ip is required. If both are provided, the command exits with an error.

The Box Image

The slowest part of an install is the box’s Docker image. A cold build on the box takes roughly 14 minutes. When a pre-built image is available, the deployment pulls it before it stops the running containers. The box keeps its services up during the download. The old containers stop only after the pull succeeds. The deployment then clears the Docker build cache, because the pulled image needs no build. When the deployment builds on the box, it keeps the build cache. A later build on the same box reuses the layers that did not change. lager uninstall clears that cache. Every release tag publishes a pre-built image, and lager install uses it by default: about 2 minutes instead of 14. This only applies when --version names a release tag. The default --version main has no published image, and neither does any other branch, so those always build on the box. Pinning a release tag is the difference between a two-minute install and a fourteen-minute one. A full 40-character commit SHA also builds on the box, and the box must be able to fetch that commit from the remote. A shorter hex value is read as a branch name. The image is verified before it is used:
  1. The computer that runs lager install resolves the tag to an immutable image digest. This step needs curl and access to ghcr.io.
  2. The box pulls that digest, pinned to its own architecture.
  3. The box checks the version label of the image. The label must name the exact version that you requested.
Any miss means a build on the box, exactly as it always did. A miss is a tag with no published image, no curl, an unreachable registry, or a missing or mismatched label. A slow install that works beats a fast one that does not. After a pull, /etc/lager/image-source records ghcr:<digest>. A build on the box removes that file. The image carries its license notices in /usr/share/licenses/lager/. That directory holds the LICENSE and NOTICE of Lager, and THIRD_PARTY.md, which lists the third-party software in the image. The pip/ directory holds the notice files of each Python package. To list them, run docker exec lager ls /usr/share/licenses/lager on the box. Pass --no-pull to skip the pre-built image and always build. To turn the install pull off for a whole shell, set LAGER_BOX_IMAGE_PULL to 0, false, no or off. The variable turns the pull on only for 1, true or yes, in any letter case, and every other value turns it off. --pull overrides the variable. lager update reads the same words in the same way. Only the default differs: install uses a pre-built image for a release tag, and update does not. --pull together with --no-pull is a usage error.

Deploy Timeout

--timeout bounds the deploy step, which includes the container build. The default is 1800 seconds. Without the flag, LAGER_INSTALL_TIMEOUT sets the value. An unparseable or negative LAGER_INSTALL_TIMEOUT gives the default. --timeout 0 removes the bound, and a negative --timeout is refused. When the budget runs out, install prints Deployment timed out after .... It also says that the build can still be healthy, and it prints retry commands with twice the budget. It then exits 1. A re-run is safe. The install lock stays valid for the larger of 3600 seconds and twice the timeout. The lock cannot be renewed while the container that serves it is down. With --timeout 0 the lock never expires, because no fixed time outlasts an unbounded deploy. An install that ends normally releases the lock. After a hard kill, such as a power loss, the lock has no expiry time to clear it, and lager boxes unlock releases it.

What Gets Installed

The host firewall does not protect the published ports. Docker installs its forwarding rules ahead of the host chain. A published port therefore answers anyone who can route to the box, whatever ufw status reports. Put the box on a VPN or an isolated LAN. See the Security Model section of SECURITY.md.
The firewall script sets a default deny policy for incoming traffic and allows SSH from anywhere. It allows the Lager ports on lo, docker0, tailscale0 (if Tailscale is up) and the --corporate-vpn interface. It denies them on every other interface. These rules govern the Lager ports only on a box set to lager box-config network-mode host, where the container does not publish ports. Each run of the script resets ufw first. A later lager install without --skip-firewall therefore removes firewall rules that you added by hand. lager update does not run the script.

Installation Flow

  1. Resolve target - Looks up box IP from --box name or uses --ip directly
  2. Verify SSH - Tests key-based authentication and settles which identity the rest of the command offers
  3. Show summary - Displays what will be installed and asks for confirmation
  4. Deploy - Runs the deployment script. About 2 minutes when the pre-built image is used; a cold build on ordinary box hardware is roughly 14 minutes (see below). The step is bounded by --timeout, 30 minutes by default — raise it on slower hardware, where a healthy build can legitimately exceed the default
  5. Store version - Writes the deployed version and the CLI version to /etc/lager/version, and the deployed ref and commit to /etc/lager/ref. It also writes the build hash to /etc/lager/build-hash and reads the version file back. If a write fails, install names the file, prints the manual fix, and exits 1. The deployment is complete at that point
  6. Check passwordless sudo - Confirms the sudoers grant that lager box-config apply needs. The deploy step normally writes it, and this step then asks for nothing. If the box does not have the grant, this step writes it and asks for the box’s sudo password. A failure here is a warning, not an install failure
  7. Add to config - Asks whether to add the box to ~/.lager. Install skips this step with --yes, and with --box (the box is already stored)

Examples

Passwordless sudo

Installation configures passwordless sudo for the box login user. The CLI drives the box over non-interactive SSH, where sudo has no terminal to prompt against. The grants must be in place before provisioning runs. Lager writes these files, and only these files, under /etc/sudoers.d/:
The box login user is root-equivalent by design. Provisioning a box requires root. Deploying udev rules runs commands as root by construction, and apt-get executes arbitrary commands as root through its own configuration. Lager writes the grants above as specific commands, to keep the blast radius small and the file readable. They are not a privilege boundary. Anyone who can log in as the box user can obtain root on that box.Treat the box login account as equivalent to root when deciding who holds its SSH key, and prefer a dedicated user on a dedicated machine.
Lager regenerates each file in full when it writes that file. That keeps a box on the current grant shape. A grant added inside one of them is lost the next time it is written, so each file opens with a header saying so.

Sudo password prompts

lager install writes both of its files in one sudo session, early in the deploy step. It asks for the box’s sudo password in that session and nowhere else: Before the session, install checks the box without a terminal. It compares /etc/lager/.deploy-sudoers-v1 with a digest of the grants that this run writes, and it runs one granted command with sudo -n. If both checks pass, install skips the session. A different --user or --corporate-vpn value changes the grants, and a newer CLI can change them too. Install then runs the session one time and asks for the password one time. To make the next install write the files again, delete /etc/lager/.deploy-sudoers-v1 on the box. lager update does not use this file, and it asks for no password. The same session installs /usr/local/lib/lager/etc_lager_perms.sh. This root-owned script creates /etc/lager and sets its owner. It does not change /etc/lager/authorized_keys.d. The grant names the script by its exact path, and the script refuses all arguments. Lager grants no find command and no recursive chown. Lager never reads, edits, or removes any other file in /etc/sudoers.d/. If you or a box-management platform need additional grants, put them in a separate file there. For example, /etc/sudoers.d/zz-local sorts after Lager’s files, so its rules win. Lager will leave that file alone, including during lager uninstall.

SSH Authentication

Install requires key-based SSH authentication. It offers ~/.ssh/lager_box — the key lager ssh-setup and lager install generate — explicitly, because that is not a filename ssh tries on its own. If the box does not accept it, install falls back to your own default identities, so a box you authorized with ssh-copy-id works unchanged. If neither authenticates, install offers to set the key up for you:
Accepting prompts for the box password once, installs the key, and every later step of the install runs unattended. --yes accepts it without asking. Declining stops the install and points you at lager ssh-setup --box <name-or-ip>, which does the same thing as a separate step. Install does not offer a password fallback for the deployment itself. A box configured with PasswordAuthentication no never receives the password. The resulting “password failed” message therefore described something that never happened. For new hosts, the SSH host key is accepted automatically. If the host key changed since a previous connection, the command asks you to verify the change manually first. Install does not write to ~/.ssh/config. Earlier versions added a per-IP Host block naming the key. Another tool that regenerated the file deleted that block, and the block also disabled host-key verification for the box. The identity is passed per command instead.

Notes

  • For a step-by-step walkthrough, see Setting Up a Lager Box
  • Requires SSH client tools (ssh, ssh-keygen) to be installed locally
  • The deployment script is bundled with the lager-cli package
  • After installation, verify connectivity with lager hello --box <name>
  • Use lager update to deploy code updates to an already-installed box
  • Use lager uninstall to remove Lager software from a box