CI runner topology¶
What it does¶
Every iiiris CI job runs on a GitLab SaaS hosted runner. No self-hosted runner is required to build, test, validate, or release the project. Linux jobs run in a project-built container image; Windows jobs run on GitLab's hosted Windows pool with a per-job MSYS2 toolchain.
Surface¶
Runner tags¶
| Tag | Used by |
|---|---|
saas-linux-medium-amd64 |
default: — every Linux job unless overridden |
saas-linux-medium-arm64 |
release-binary-smoke-arm64, release-binary-linux-arm64, release-image-arm64 |
saas-windows-medium-amd64 |
build-windows-amd64, build-windows-msi |
Images¶
| Image | Built by | Consumed by |
|---|---|---|
$CI_REGISTRY_IMAGE/iiiris-ci:latest |
ci-image |
default: — all plain Linux jobs |
$CI_REGISTRY_IMAGE/iiiris-ci:$CI_COMMIT_SHA |
ci-image |
traceability only |
$CI_REGISTRY_IMAGE/iiiris-ci:e2e |
ci-image |
iiif-viewer-e2e |
python:3.13 |
upstream | pages |
docker:27 + docker:27-dind |
upstream | the .dind job base |
iiiris-ci:latest is multi-arch (linux/amd64,linux/arm64), so amd64
and arm64 jobs pull the same tag. iiiris-ci:e2e is amd64-only — the one
job that uses it has no arm64 counterpart.
Job shapes¶
Three shapes, no others:
- Plain container job — inherits
default:; the script runs directly iniiiris-ci:latest. Nodocker runwrapper: the repo is already the working directory. .dindjob —image: docker:27+services: [docker:27-dind]for jobs that build or push container images (ci-image,docker-build*,release-image*).- Windows job —
tags: [saas-windows-medium-amd64], PowerShell script, toolchain installed per job.
Contracts¶
- No self-hosted runner is referenced.
.gitlab-ci.ymlcontains notags: [iiiris]. Any reintroduction is a regression. ci-imageis gated, not per-pipeline. It runs only on changes todeploy/ci/**or.gitlab-ci.yml, or on release tags; otherwise it is absent from the pipeline (when: never). Dependent jobs declareneeds: [{job: ci-image, optional: true}]so a pipeline that skips it consumes the previously published:latest.- A hand-forced rebuild is a separate job,
ci-image-rebuild, and nothing mayneeds:it. The two must not be merged back into one job with awhen: manualfallback rule.optional: truelets a dependent run when the needed job is absent; it does not when the job is present but unplayed. A single job with a manual fallback therefore sat in every feature-MR pipeline holdinglint,testand all four conformance jobs inskipped— while the pipeline reportedsuccess. Any change that puts a manualci-imageback in the dependency graph reintroduces a green check that means "nothing ran". - dind runs plain-TCP, not TLS.
.dindsets--tls=false,DOCKER_HOST: tcp://docker:2375,DOCKER_TLS_CERTDIR: "".docker buildx createfails with TLS data loaded from the environment ("could not create a builder instance with TLS data loaded from environment"); plain TCP is what makes buildx usable inside dind. ci-imagelayers are cached in the registry.--cache-to/--cache-from type=registry,ref=…:buildcache,mode=max(and:e2ecachefor the e2e image). A no-op rebuild reuses layers instead of recompiling the libvips-linked toolchain.- arm64 builds are native, not emulated.
release-binary-*-arm64runs on an arm64 runner withCGO_ENABLED=1;release-image-arm64builds on an arm64 runner. Onlyci-imageandrelease-image-debianuse QEMU (tonistiigi/binfmt) to produce a multi-arch manifest in one job.release-imagefuses the two per-arch tags withdocker buildx imagetools create— it does not rebuild. - The e2e browser is upstream's
headless-shell, not Debian'schromium. Debian bookworm'schromium(150.0.7871.46) SIGTRAPs on startup inside a container regardless of--no-sandbox,--disable-dev-shm-usage,--no-zygote,--single-process, orseccomp=unconfined; it dies before printing the DevTools endpoint chromedp waits for.deploy/ci/Dockerfile.e2ecopies/headless-shellout ofchromedp/headless-shelland puts it onPATH, where chromedp's exec-path probe finds it ahead of any other browser name. - Windows jobs must kill their stragglers. GitLab's Windows
PowerShell executor will not finish a job while any process still holds
the job's stdout handle.
build-windows-amd64terminatesiiirisdplus everything running out of the MSYS2 tree (pacman-keyleavesgpg-agent/dirmngrdaemons behind). Without this the job burns to its 1 h timeout after a fully successful build. - Windows cadence is gated. The MSYS2 + Go toolchain install is a
~15–20 min cold start, so
build-windows-amd64runs on release tags and on MRs touching Windows-relevant paths — not every push.build-windows-msiand the release re-export jobs are tag-only.
Out of scope¶
- Daemonless image builders (kaniko / buildah). Would avoid dind's
privileged mode but rewrites every
docker build/buildxinvocation and complicates the multi-arch release build. dind works on SaaS runners; revisit only if policy blocks it. - Self-hosted runners. Retired. The
iiiris-tagged runners may stay registered as a temporary rollback path but are not referenced by CI. - Ephemeral-EC2 Windows builds. Retired in favour of the hosted
Windows pool.
deploy/aws,deploy/packer, andscripts/awsremain in the repo but are no longer wired into CI. - macOS and Windows-ARM runners. Not offered by the SaaS pools.
macOS users build locally (
brew install vips && make build). - GitLab
cache:for the Go module/build cache. Coldgo buildon a hosted runner is tolerable today; add it iftest/lintwall-clock becomes the bottleneck.