vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
| 1 | # Image reference |
| 2 | |
| 3 | This page lists every field Vole accepts in a `.vole/images/*.yaml` definition, |
| 4 | with its defaults and limits. For how workflows reference these definitions, see |
| 5 | [Workflow reference § image-file](workflow-reference.md#image-file). For the |
| 6 | task-oriented introduction, see [Workflows § Images](../workflows.md#images). |
| 7 | |
| 8 | Unknown keys are rejected. Every definition under `.vole/images/` is parsed when a |
| 9 | workflow is pushed, even if no job references it, so a broken definition fails the |
| 10 | push that introduced it. |
| 11 | |
| 12 | ## File location and limits |
| 13 | |
| 14 | | Item | Value | |
| 15 | |---|---| |
| 16 | | Location | Directly under `.vole/images/`; subdirectories are rejected | |
| 17 | | Extension | `.yaml` or `.yml` | |
| 18 | | Reference | `image-file: <name>`; the extension is optional in the reference | |
| 19 | | Maximum file size | 64 KiB | |
| 20 | | Schema version | `1` | |
| 21 | | Maximum `env` entries | 64 | |
| 22 | | Maximum `run` entries | 100 | |
| 23 | | Maximum single command size | 64 KiB after trimming | |
| 24 | | Maximum env value size | 8 KiB | |
| 25 | |
| 26 | A definition is named by its filename without the extension: |
| 27 | |
| 28 | ```text |
| 29 | .vole/images/go.yaml -> image-file: go |
| 30 | .vole/images/go.yml -> image-file: go |
| 31 | .vole/images/release.yaml -> image-file: release |
| 32 | ``` |
| 33 | |
| 34 | Names are 1–64 characters: lowercase letters, digits, `.`, `_` and `-`, starting |
| 35 | with a letter or digit. Two files for one name (`go.yaml` and `go.yml`) are |
| 36 | rejected as ambiguous. |
| 37 | |
| 38 | ## Top-level fields |
| 39 | |
| 40 | | Field | Type | Required | Default | Meaning | |
| 41 | |---|---|---|---|---| |
| 42 | | `version` | integer | yes | — | Schema version. Must be `1`. | |
| 43 | | `from` | string | yes | — | Base OCI image. A tag works; a digest (`image@sha256:...`) makes the build reproducible. | |
| 44 | | `env` | mapping | no | none | Environment variables set in the image. | |
| 45 | | `run` | list of strings | no | none | Build commands, applied in order. | |
| 46 | |
| 47 | ```yaml |
| 48 | version: 1 |
| 49 | from: golang:1.27-bookworm |
| 50 | env: |
| 51 | CGO_ENABLED: "0" |
| 52 | run: |
| 53 | - apt-get update && apt-get install -y --no-install-recommends jq |
| 54 | - go install example.com/tool@v1.2.3 |
| 55 | ``` |
| 56 | |
| 57 | ### `version` |
| 58 | |
| 59 | The only supported value is `1`: |
| 60 | |
| 61 | ```yaml |
| 62 | version: 1 |
| 63 | ``` |
| 64 | |
| 65 | ### `from` |
| 66 | |
| 67 | The base image for the definition. It uses the same image-reference validation as |
| 68 | `image:` in a workflow: |
| 69 | |
| 70 | - Must be non-empty and at most 255 bytes. |
| 71 | - Must start with a letter or digit. |
| 72 | - May contain letters, digits, `.`, `_`, `/`, `:`, `@`, `+`, `~` and `-`. |
| 73 | - A leading `-`, `..`, `://`, or a `file:` prefix is rejected. |
| 74 | |
| 75 | ```yaml |
| 76 | from: golang:1.27-bookworm@sha256:... |
| 77 | ``` |
| 78 | |
| 79 | Pin the base image by digest when reproducibility matters. A mutable tag can |
| 80 | change what a later build produces. |
| 81 | |
| 82 | ### `env` |
| 83 | |
| 84 | A mapping of environment variable names to values, applied to the image: |
| 85 | |
| 86 | ```yaml |
| 87 | env: |
| 88 | CGO_ENABLED: "0" |
| 89 | MESSAGE: "hello world" |
| 90 | ``` |
| 91 | |
| 92 | | Constraint | Value | |
| 93 | |---|---| |
| 94 | | Maximum entries | 64 | |
| 95 | | Key syntax | `^[A-Za-z_][A-Za-z0-9_]*$` | |
| 96 | | Maximum value size | 8 KiB | |
| 97 | | Line endings | Values must be one line; `CR`/`LF` are rejected | |
| 98 | |
| 99 | Keys are emitted in sorted order, so the generated build is deterministic. |
| 100 | |
| 101 | ### `run` |
| 102 | |
| 103 | Build commands, run in order after `from` and `env`: |
| 104 | |
| 105 | ```yaml |
| 106 | run: |
| 107 | - apt-get update && apt-get install -y --no-install-recommends jq |
| 108 | - go install example.com/tool@v1.2.3 |
| 109 | ``` |
| 110 | |
| 111 | | Constraint | Value | |
| 112 | |---|---| |
| 113 | | Maximum entries | 100 | |
| 114 | | Empty commands | Rejected | |
| 115 | | Maximum size | 64 KiB after trimming | |
| 116 | | Line endings | One line; `CR`/`LF` are rejected | |
| 117 | | Shell | Each command runs through `/bin/sh -c` | |
| 118 | |
| 119 | Chain commands with `&&` rather than using multiple lines: |
| 120 | |
| 121 | ```yaml |
| 122 | run: |
| 123 | - apt-get update && apt-get install -y --no-install-recommends git make |
| 124 | ``` |
| 125 | |
| 126 | `run` entries are rendered as Dockerfile exec-form instructions: |
| 127 | |
| 128 | ```dockerfile |
| 129 | RUN ["/bin/sh", "-c", "apt-get update && apt-get install -y --no-install-recommends git make"] |
| 130 | ``` |
| 131 | |
| 132 | That keeps the first word of a command from being interpreted as a Dockerfile |
| 133 | build flag such as `--network=host`. |
| 134 | |
| 135 | ## What the definition can and cannot express |
| 136 | |
| 137 | The format deliberately supports only `from`, `env`, and `run`. It does not |
| 138 | support: |
| 139 | |
| 140 | - `COPY`, `ADD`, or a build context from the repository |
| 141 | - multi-stage builds |
| 142 | - `ARG` |
| 143 | - `WORKDIR`, `USER`, `ENTRYPOINT`, `CMD`, `HEALTHCHECK`, or `SHELL` |
| 144 | - build secrets or host mounts |
| 145 | |
| 146 | The runner controls the job's working directory (`/workspace`), user, entrypoint, |
| 147 | and mounts. The image supplies the toolchain and the shell. |
| 148 | |
| 149 | The image build uses the runtime's default network. `--container-network` applies |
| 150 | to the job container, not to the image build. The builder has no host mounts and |
| 151 | no build secrets from Vole. |
| 152 | |
| 153 | ## Execution and caching |
| 154 | |
| 155 | The runner renders the definition to a Dockerfile: |
| 156 | |
| 157 | ```dockerfile |
| 158 | FROM <from> |
| 159 | ENV KEY="value" |
| 160 | RUN ["/bin/sh", "-c", "<command>"] |
| 161 | ... |
| 162 | ``` |
| 163 | |
| 164 | It builds that image with the configured OCI runtime and tags it by the SHA-256 of |
| 165 | the exact definition file. A later job with the same definition reuses the tag. |
| 166 | `--container-pull always` rebuilds a custom image even when the tag exists. |
| 167 | |
| 168 | An image definition is code: its build runs on the runner through the OCI builder, |
| 169 | so approving a contributor tip that changes a definition also approves that build. |
| 170 | Use rootless Podman where possible. |
| 171 | |
| 172 | The resulting job image must contain `/bin/sh` and a POSIX `sleep`, because Vole |
| 173 | runs each step with `/bin/sh -c` and keeps the container alive between steps. If |
| 174 | `run` is non-empty, the base image used for `from` must also provide `/bin/sh`, |
| 175 | because build commands are rendered that way. |
| 176 | |
| 177 | Nothing a job writes inside the container survives the run, except what it puts in |
| 178 | the cache: the runner mounts a directory at `/cache` and sets no variables for it, |
| 179 | so `env` is where a definition opts in. Point the toolchain's own variables at it |
| 180 | and its downloads are reused between jobs: |
| 181 | |
| 182 | ```yaml |
| 183 | env: |
| 184 | GOMODCACHE: "/cache/go-mod" |
| 185 | GOCACHE: "/cache/go-build" |
| 186 | ``` |
| 187 | |
| 188 | A job may also set its own `env`, which is applied after the image's and wins; it |
| 189 | is the way to point at `/cache` when the image is a registry reference rather than a |
| 190 | definition. `/tmp` is a memory-backed tmpfs and `HOME` points there for everything |
| 191 | else; the XDG convention (`XDG_CACHE_HOME: /cache/xdg`) covers tools that follow it. |
| 192 | See [Runners § Caching](../runner.md#caching). |
| 193 | Check that anything calling `getpwuid` has a passwd entry for the uid the job runs |
| 194 | as — see [Runners § How a job runs](../runner.md#how-a-job-runs). |
| 195 | |
| 196 | ## Validation and pinning |
| 197 | |
| 198 | - `version` must be `1`. |
| 199 | - Unknown keys are errors. |
| 200 | - The definition must live directly under `.vole/images/`, not in a subdirectory. |
| 201 | - Both `.yaml` and `.yml` are accepted, but not two files for the same name. |
| 202 | - Every definition is parsed at push time, even if unreferenced. |
| 203 | - The exact definition text and its digest are pinned with each run. |
| 204 | - `--container-pull always` rebuilds instead of reusing the digest tag. |
| 205 | |
| 206 | ## Complete example |
| 207 | |
| 208 | ```yaml |
| 209 | # .vole/images/release-toolchain.yaml |
| 210 | version: 1 |
| 211 | from: golang:1.27-bookworm@sha256:... |
| 212 | env: |
| 213 | CGO_ENABLED: "0" |
| 214 | SOURCE_DATE_EPOCH: "0" |
| 215 | run: |
| 216 | - apt-get update && apt-get install -y --no-install-recommends git make jq |
| 217 | - go install example.com/release-tool@v1.4.0 |
| 218 | ``` |
| 219 | |
| 220 | ```yaml |
| 221 | # .vole/workflows/release.yaml |
| 222 | version: 1 |
| 223 | on: |
| 224 | push: |
| 225 | tags: ["v*"] |
| 226 | jobs: |
| 227 | release: |
| 228 | runs-on: [linux, amd64] |
| 229 | image-file: release-toolchain |
| 230 | steps: |
| 231 | - name: build |
| 232 | run: make dist |
| 233 | artifacts: |
| 234 | - path: dist/* |
| 235 | release: true |
| 236 | ``` |
| 237 |