Skip to main content
A Lager test is the same command whether you type it or a pipeline does: lager python my_test.py --box <BOX>. What changes in CI is everything around that command — signing in without a prompt, naming the box, proving the box is reachable and running the code you think it is, and sharing it with everyone else’s jobs. This page walks through those pieces. It uses GitHub Actions for the examples; the CLI behaves the same under any runner.

Where the job runs

A hosted runner can drive a Lager Box as long as it can reach the box over the network. If the box sits on a private network — which is usual, because it is wired to hardware — use a self-hosted runner with that access:
The box itself does the work in either case. lager python ships the script to the box and runs it there, so the runner only needs the CLI and a route.

Signing in without a prompt

A box behind an access gateway needs a session. lager login takes the credentials as options so it never prompts:
One login covers the whole job: the session is stored in ~/.lager_gateway_auth and refreshes itself for the rest of the run.
Read the password from a secret, never a literal. A password on the command line is visible in the process list and lands in logs.Use a dedicated CI account without MFA. --email and --password are not enough on an MFA-enabled account: the CLI still prompts for the code, and a pipeline has nobody to answer it.
A box with no gateway in front of it needs no sign-in at all — skip this step.

Naming the box

Register the box once per job, then refer to it by name:
Keep the box name in a repository variable and the IP in a secret, so a bench move is a settings change rather than a commit.

Two checks worth failing on

These are the difference between a red pipeline that means something and a green one that does not.

Is the box reachable?

The retry is deliberate. The first authenticated contact after a fresh login can fail exactly once while the gateway records the box-to-auth-server link; the second attempt is expected to pass.

Is the box running the code under test?

This one is easy to skip and expensive to skip. lager python runs your script on the box, against the box’s own checkout. If the box is running last week’s code, the run still passes or fails — it just is not evidence about your commit.
--check is a dry run: it reports what would change without touching the box. Treat a difference as a hard failure rather than a warning. A bench result from a stale box is worse than no result, because it looks like a result.

Sharing the box between jobs

Lager takes a lock automatically. Every hardware-interacting command acquires one as it resolves the box and releases it when the command exits, including on error — so a CI job needs no special invocation:
Each matrix item gets a distinct lock holder. Whichever loses the race waits rather than failing: in CI the default is a patient queue (LAGER_LOCK_WAIT, 30 minutes), where an interactive shell fails fast instead. Add a concurrency group as well, so two runs of the same workflow do not queue against each other for the full timeout:
Locking covers holder identity, TTL and heartbeat, and the LAGER_LOCK_* overrides in full.

Putting it together

Running arbitrary commands

lager python is the test runner. To run a build or a tool inside the box’s container instead, use lager exec:
lager exec takes the same automatic lock, so it interleaves safely with test jobs.

Notes

  • Pin the CLI version you install (pip install lager-cli==X.Y.Z) if you want a pipeline that does not change underneath you.
  • A detached run keeps the lock. lager python --detach hands the lock to the box so the work survives the job ending — which also means the box stays locked until that work finishes.
  • LAGER_AUTO_LOCK_DISABLE=1 turns the auto-lock off. Do not set it on a shared bench; it exists for single-user setups where the lock is pure overhead.