vole
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.
Require an image for every CI job
70b702649b875d841e294f5a085ca784d43e5bc4 · Keenan Nemetz · 15d ago · parent 7da9111e
Require an image for every CI job Runners now require an OCI runtime, and each workflow job must name either a registry image or an image-file definition under .vole/images. Definitions are validated at push time, pinned with the run, built locally by the runner, and cached by definition digest. Records and the run page carry the image and definition digest. Split the CI guide into workflows and runners, and add reference pages for workflow and image definition fields.
docs/reference/image-reference.md +217
| @@ -0,0 +1,217 @@ | |||
| 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. | | ||
| 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 | ## Validation and pinning | ||
| 178 | |||
| 179 | - `version` must be `1`. | ||
| 180 | - Unknown keys are errors. | ||
| 181 | - The definition must live directly under `.vole/images/`, not in a subdirectory. | ||
| 182 | - Both `.yaml` and `.yml` are accepted, but not two files for the same name. | ||
| 183 | - Every definition is parsed at push time, even if unreferenced. | ||
| 184 | - The exact definition text and its digest are pinned with each run. | ||
| 185 | - `--container-pull always` rebuilds instead of reusing the digest tag. | ||
| 186 | |||
| 187 | ## Complete example | ||
| 188 | |||
| 189 | ```yaml | ||
| 190 | # .vole/images/release-toolchain.yaml | ||
| 191 | version: 1 | ||
| 192 | from: golang:1.27-bookworm@sha256:... | ||
| 193 | env: | ||
| 194 | CGO_ENABLED: "0" | ||
| 195 | SOURCE_DATE_EPOCH: "0" | ||
| 196 | run: | ||
| 197 | - apt-get update && apt-get install -y --no-install-recommends git make jq | ||
| 198 | - go install example.com/release-tool@v1.4.0 | ||
| 199 | ``` | ||
| 200 | |||
| 201 | ```yaml | ||
| 202 | # .vole/workflows/release.yaml | ||
| 203 | version: 1 | ||
| 204 | on: | ||
| 205 | push: | ||
| 206 | tags: ["v*"] | ||
| 207 | jobs: | ||
| 208 | release: | ||
| 209 | runs-on: [linux, amd64] | ||
| 210 | image-file: release-toolchain | ||
| 211 | steps: | ||
| 212 | - name: build | ||
| 213 | run: make dist | ||
| 214 | artifacts: | ||
| 215 | - path: dist/* | ||
| 216 | release: true | ||
| 217 | ``` | ||