Runner Environment
Every ritual step runs on a fresh, ephemeral machine. This page documents exactly what that machine has on it, so you never have to find out by pushing.
How the base image is chosen
Section titled “How the base image is chosen”In order of precedence:
environment.base_image— an explicit image. Always wins.environment.language+environment.version— the default image for that language, at the version you asked for.- Neither set →
buildpack-deps:bookworm-curl.
environment: language: go version: "1.25" # → golang:1.25-bookwormDefault images
Section titled “Default images”All Debian (“bookworm”) based. Debian, not Alpine: prebuilt vendor binaries, native
node modules, and the cloud SDKs assume glibc, and some tools (notably gcloud)
aren’t packaged for Alpine at all.
language | Image | Default version |
|---|---|---|
go | golang:{version}-bookworm | 1.25 |
node | node:{version}-bookworm | 22 |
python | python:{version}-bookworm | 3.12 |
rust | rust:{version}-bookworm | 1 |
ruby | ruby:{version}-bookworm | 3 |
java | eclipse-temurin:{version}-jdk | 21 |
php | php:{version}-cli-bookworm | 8.3 |
| (unset / unknown) | buildpack-deps:bookworm-curl | — |
version must look like a version ([A-Za-z0-9._-]); anything else is ignored and
the default is used, rather than producing a bogus image reference.
Each image ships that language’s toolchain, plus git, curl, wget, and a shell.
It does not ship other languages. A Go image has no Node; a Node image has no Go.
For a polyglot repo, add what you need with packages: (below) or bring your own
image with base_image.
environment.packages
Section titled “environment.packages”Installs OS packages on the machine before any step runs:
environment: language: go packages: [nodejs, gcloud]Names map to what you actually need, not to a literal package name. Notably:
| You write | What gets installed | Checked via |
|---|---|---|
nodejs / node | nodejs and npm | node |
npm | nodejs and npm | npm |
python / python3 | python3, python3-pip | python3 |
pip / pip3 | python3-pip | pip3 |
docker | docker.io | docker |
gcloud | Google Cloud CLI (official tarball, symlinked onto PATH) | gcloud |
| anything else | that package, as-is | that binary |
The nodejs → nodejs + npm mapping matters: on Debian and Alpine, the nodejs
package ships only the node binary. npm is a separate package. Asking for
nodejs and getting sh: npm: not found is the single most common way to lose an
afternoon here, so packages: [nodejs] now gives you both.
A package that can’t be installed fails the run immediately, with the reason in a
synthetic install-packages step — rather than failing three steps later with a
confusing command not found.
Escape hatch: bring your own image
Section titled “Escape hatch: bring your own image”packages: installs on every run, on a machine that is thrown away afterwards —
there is no dependency cache yet. If the install cost is hurting, pre-bake an image:
environment: base_image: ghcr.io/my-org/ci:2026-07Any image works, including slim ones: the machine installs curl itself if the image
has neither curl nor wget.
How the runner gets there
Section titled “How the runner gets there”The runner binary is not baked into the image. The machine boots your base image,
downloads rituals-service from the data-plane (authenticated with the per-run scoped
token), and execs it. Two consequences worth knowing:
- The base image is entirely your choice — it never needs anything VeloGit-specific.
- Runner fixes ship with a data-plane deploy; there’s no image for you to rebuild.
Known gaps
Section titled “Known gaps”- No dependency caching. Every run does a cold
npm ci/go mod download, andpackages:reinstalls from scratch. The workspace is a temp dir on a machine that is destroyed at exit. Pre-baking abase_imageis the workaround today. environment.versiononly selects the image tag. It doesn’t install a second toolchain into an image that lacks it.