Run a line in your own runner image
Each run starts from its line's image. To have your toolchain and your
repository's dependencies already there, you need a line managed through
Terraform or the resource API, a registry your hosts can pull from, and access
to each host's host.env.
Build the image
Build FROM Spotlight's runner image, at the release your hosts run. Keep
these rules:
- Build as
agent(uid 2000), or hand what you bake toagent. Switch back torootat the end, since the runner starts as root. - Bake each repository's checkout at
/workspace/<name>, where<name>is the repository's name without its owner. A run then only fetches and switches branches. - Keep the harnesses the base image holds. A run goes only to an image that holds its station's harness.
For a Node.js repository acme/web:
FROM ghcr.io/usespotlightai/spotlight-runner:<release>
USER agent
RUN git clone https://github.com/acme/web /workspace/web
WORKDIR /workspace/web
RUN npm ci
USER rootFor a Go repository acme/api, with Go copied in from its official image:
FROM golang:1.26-bookworm AS go
FROM ghcr.io/usespotlightai/spotlight-runner:<release>
COPY --from=go /usr/local/go /usr/local/go
ENV PATH=/usr/local/go/bin:$PATH GOPATH=/home/agent/go
USER agent
RUN git clone https://github.com/acme/api /workspace/api \
&& cd /workspace/api && go mod download && go build ./...
USER rootFor a Python repository acme/models, install system packages as root first,
then build the virtual environment as agent:
FROM ghcr.io/usespotlightai/spotlight-runner:<release>
RUN apt-get update && apt-get install -y --no-install-recommends python3-venv \
&& rm -rf /var/lib/apt/lists/*
USER agent
RUN git clone https://github.com/acme/models /workspace/models \
&& python3 -m venv /workspace/models/.venv \
&& /workspace/models/.venv/bin/pip install -r /workspace/models/requirements.txt
USER rootFor a Rust repository acme/engine, set CARGO_TARGET_DIR outside the
checkout, so build output survives the checkout being replaced by a restored
workspace:
FROM ghcr.io/usespotlightai/spotlight-runner:<release>
USER agent
RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal
ENV PATH=/home/agent/.cargo/bin:$PATH CARGO_TARGET_DIR=/home/agent/target
RUN git clone https://github.com/acme/engine /workspace/engine \
&& cd /workspace/engine && cargo fetch && cargo build
USER rootIf a station's harness profile
starts MCP servers with npx, install them in the image too, so runs don't
fetch them each time.
Clone a private repository with your build's own credentials. Push the image to your registry. If the registry is private, set up the hosts as in Pull runner images from a private registry.
To check which runner release the image holds, run it with harnesses '[]':
docker run --rm ghcr.io/acme/web-runner:main harnesses '[]'It prints the release as release in a line of JSON; ignore the empty
harnesses list. A host refuses an image whose runner is older than Spotlight needs,
so rebuild on a newer base when that happens.
Name the image on the line
Set image on the line's repository block. Add setup if the checkout needs
a command before each run, such as installing what changed since the image was
built:
resource "spotlight_line" "core_delivery" {
team_id = spotlight_team.core.id
key = "delivery"
name = "Delivery"
repository {
name = "acme/web"
image = "ghcr.io/acme/web-runner:main"
setup = "npm ci"
}
# stations as before
}In the resource API, the fields are image and setup on each entry of
repositories. A line names at most one image: if it has several
repositories, they name the same image or none.
Let the hosts serve it
A host serves only the images its SPOTLIGHT_RUNNER_IMAGE admits. Open the
host's host.env and add your image to that line after the entry already
there, separated by a comma. An entry is an exact image, or a prefix ending in
* that admits every image under it:
SPOTLIGHT_RUNNER_IMAGE=ghcr.io/usespotlightai/spotlight-runner:<release>,ghcr.io/acme/*Keep the first entry as it is: the first exact image is the default for lines that name none. Then restart the host.
Reinstalling resets this line to the default image, so add your entry back after running the install script again.
Check that it is served
The host checks every minute which images lines name. Open Settings from
the workspace menu, then Runners. In the host's row, Images shows the default image and +N for
the others; hover over the image to see every name. If it also reads
N refused, hover over that for each refused image and its reason. A refusal
saying the host's SPOTLIGHT_RUNNER_IMAGE does not admit it means the entry
does not match the name on the line.
The host pulls the image again before every run, so a tag you push again reaches the next run without a restart.
Related
- Repository fields, in the line reference.
- Inside a sandbox: what a run sees inside the image.
- Why every run gets a fresh sandbox