Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Image reference
This page lists every field Vole accepts in a .vole/images/*.yaml definition,
with its defaults and limits. For how workflows reference these definitions, see
Workflow reference § image-file. For the
task-oriented introduction, see Workflows § Images.
Unknown keys are rejected. Every definition under .vole/images/ is parsed when a
workflow is pushed, even if no job references it, so a broken definition fails the
push that introduced it.
File location and limits
| Item | Value |
|---|---|
| Location | Directly under .vole/images/; subdirectories are rejected |
| Extension | .yaml or .yml |
| Reference | image-file: <name>; the extension is optional in the reference |
| Maximum file size | 64 KiB |
| Schema version | 1 |
Maximum env entries |
64 |
Maximum run entries |
100 |
| Maximum single command size | 64 KiB after trimming |
| Maximum env value size | 8 KiB |
A definition is named by its filename without the extension:
.vole/images/go.yaml -> image-file: go
.vole/images/go.yml -> image-file: go
.vole/images/release.yaml -> image-file: release
Names are 1–64 characters: lowercase letters, digits, ., _ and -, starting
with a letter or digit. Two files for one name (go.yaml and go.yml) are
rejected as ambiguous.
Top-level fields
| Field | Type | Required | Default | Meaning |
|---|---|---|---|---|
version |
integer | yes | — | Schema version. Must be 1. |
from |
string | yes | — | Base OCI image. A tag works; a digest (image@sha256:...) makes the build reproducible. |
env |
mapping | no | none | Environment variables set in the image. |
run |
list of strings | no | none | Build commands, applied in order. |
version: 1
from: golang:1.27-bookworm
env:
CGO_ENABLED: "0"
run:
- apt-get update && apt-get install -y --no-install-recommends jq
- go install example.com/tool@v1.2.3
version
The only supported value is 1:
version: 1
from
The base image for the definition. It uses the same image-reference validation as
image: in a workflow:
- Must be non-empty and at most 255 bytes.
- Must start with a letter or digit.
- May contain letters, digits,
.,_,/,:,@,+,~and-. - A leading
-,..,://, or afile:prefix is rejected.
from: golang:1.27-bookworm@sha256:...
Pin the base image by digest when reproducibility matters. A mutable tag can change what a later build produces.
env
A mapping of environment variable names to values, applied to the image:
env:
CGO_ENABLED: "0"
MESSAGE: "hello world"
| Constraint | Value |
|---|---|
| Maximum entries | 64 |
| Key syntax | ^[A-Za-z_][A-Za-z0-9_]*$ |
| Maximum value size | 8 KiB |
| Line endings | Values must be one line; CR/LF are rejected |
Keys are emitted in sorted order, so the generated build is deterministic.
run
Build commands, run in order after from and env:
run:
- apt-get update && apt-get install -y --no-install-recommends jq
- go install example.com/tool@v1.2.3
| Constraint | Value |
|---|---|
| Maximum entries | 100 |
| Empty commands | Rejected |
| Maximum size | 64 KiB after trimming |
| Line endings | One line; CR/LF are rejected |
| Shell | Each command runs through /bin/sh -c |
Chain commands with && rather than using multiple lines:
run:
- apt-get update && apt-get install -y --no-install-recommends git make
run entries are rendered as Dockerfile exec-form instructions:
RUN ["/bin/sh", "-c", "apt-get update && apt-get install -y --no-install-recommends git make"]
That keeps the first word of a command from being interpreted as a Dockerfile
build flag such as --network=host.
What the definition can and cannot express
The format deliberately supports only from, env, and run. It does not
support:
COPY,ADD, or a build context from the repository- multi-stage builds
ARGWORKDIR,USER,ENTRYPOINT,CMD,HEALTHCHECK, orSHELL- build secrets or host mounts
The runner controls the job’s working directory (/workspace), user, entrypoint,
and mounts. The image supplies the toolchain and the shell.
The image build uses the runtime’s default network. --container-network applies
to the job container, not to the image build. The builder has no host mounts and
no build secrets from Vole.
Execution and caching
The runner renders the definition to a Dockerfile:
FROM <from>
ENV KEY="value"
RUN ["/bin/sh", "-c", "<command>"]
...
It builds that image with the configured OCI runtime and tags it by the SHA-256 of
the exact definition file. A later job with the same definition reuses the tag.
--container-pull always rebuilds a custom image even when the tag exists.
An image definition is code: its build runs on the runner through the OCI builder, so approving a contributor tip that changes a definition also approves that build. Use rootless Podman where possible.
The resulting job image must contain /bin/sh and a POSIX sleep, because Vole
runs each step with /bin/sh -c and keeps the container alive between steps. If
run is non-empty, the base image used for from must also provide /bin/sh,
because build commands are rendered that way.
Nothing a job writes inside the container survives the run, except what it puts in
the cache: the runner mounts a directory at /cache and sets no variables for it,
so env is where a definition opts in. Point the toolchain’s own variables at it
and its downloads are reused between jobs:
env:
GOMODCACHE: "/cache/go-mod"
GOCACHE: "/cache/go-build"
A job may also set its own env, which is applied after the image’s and wins; it
is the way to point at /cache when the image is a registry reference rather than a
definition. /tmp is a memory-backed tmpfs and HOME points there for everything
else; the XDG convention (XDG_CACHE_HOME: /cache/xdg) covers tools that follow it.
See Runners § Caching.
Check that anything calling getpwuid has a passwd entry for the uid the job runs
as — see Runners § How a job runs.
Validation and pinning
versionmust be1.- Unknown keys are errors.
- The definition must live directly under
.vole/images/, not in a subdirectory. - Both
.yamland.ymlare accepted, but not two files for the same name. - Every definition is parsed at push time, even if unreferenced.
- The exact definition text and its digest are pinned with each run.
--container-pull alwaysrebuilds instead of reusing the digest tag.
Complete example
# .vole/images/release-toolchain.yaml
version: 1
from: golang:1.27-bookworm@sha256:...
env:
CGO_ENABLED: "0"
SOURCE_DATE_EPOCH: "0"
run:
- apt-get update && apt-get install -y --no-install-recommends git make jq
- go install example.com/release-tool@v1.4.0
# .vole/workflows/release.yaml
version: 1
on:
push:
tags: ["v*"]
jobs:
release:
runs-on: [linux, amd64]
image-file: release-toolchain
steps:
- name: build
run: make dist
artifacts:
- path: dist/*
release: true