Repository navigation
v0.29.0
·
109 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
v0.29.0 - 2026-11-09
Added
- Native backend: a job container's named volumes outlive the job as Docker's do, kept in the native store per repository and trust scope, filled from the image when new, read-only with
:ro, and evicted like the cache volumes - Native backend: a job container's host-path volumes, off by default, bound only from the directories RUNNER_NATIVE_HOST_PATHS allows (never for untrusted jobs), nosuid and nodev, read-only with
:ro, idmapped through the volumes' id range; a path not allowed fails the job naming the setting - The runner can pull the default job image already provisioned, built by a new "provisioned image" workflow for linux/amd64 and linux/arm64 from the runner's own provisioning script and pinned by digest, instead of running apt-get on every new machine and empty store (22-45 s, and Ubuntu's mirrors). A pull that fails provisions as before and "Set up job" says so; RUNNER_PROVISIONED_IMAGE=false never pulls it. Nothing is pinned yet, so nothing changes until a maintainer publishes the images and pins them (provisioned-pin.sh).
- Native backend: a job container's
ports:are published from the host by a relay of the runner's (TCP and UDP), on 127.0.0.1 unless RUNNER_NATIVE_PORT_ADDRESSES allows other addresses, with a bare port given one the kernel picks and reported asdocker portdoes; no port below 1024, none for untrusted jobs - The nightly fuzz job has a zstd target that holds the runner's own zstd decoder to std's on every input (the same bytes or both an error, within a window limit, a heap limit and an output cap, and no hang), seeded with frames of every literals and sequences mode, and a test that flips each bit of the small seeds. It found three things std's decoder takes that libzstd refuses (an offset past the window, a Huffman tree that is not complete, a literal stream not used up), which the runner's decoder refuses with errors of their own; the decoder also now refuses a block over its window and a frame that is not the size its header says
- A Docker action whose image runs as a non-root USER runs on the native backend on a root runner: the workspace, home, file commands and event are the image's user's through an idmap of the job's layers that swaps it with the job's user, so what the action writes is the job's user's afterwards (#351)
- Native job containers honour more of
options::--hostname,--add-host,--shm-size,--tmpfs(nosuid and nodev, noexec unless asked),--memory(within the job's cgroup limit, never above it) and--init(#352). Security-sensitive flags (--privileged,--cap-add,--security-opt,--device,--network,--pid,--ipc,--userns) stay refused, naming the flag. A native job no longer logs the Dagger path's "options not supported here and ignored" warning for settings it carries out.
Changed
- Native image pulls unpack several times faster: zstd layers use a decoder of the runner's own (std's took 19 s for a 500 MB layer, now 1 s), a layer's inflating and hashing run on a thread of their own beside the unpacking, the layers' blobs are fetched ahead while the earlier ones unpack, and large files start their writeback as they are written so the store's syncfs has little left to wait for
- CI scripts stream their progress into the job log as they run (tlc.sh and spin.sh per configuration, zig-stress.sh per round, store-crash.sh every 25 crash points, the comparison scripts per phase), not only a summary at the end
- Valgrind in CI stops a run whose log has not grown for ten minutes (eight for a pull request's changed tests) and names the test that was running, instead of waiting for the job's limit
- The README is now a short overview; contributing (set-up, hooks, versioning, changelog) moved to CONTRIBUTING.md, the CI pipeline to docs/CI.md, and running the runner (trust, caches, deploying, memory) to docs/OPERATIONS.md
- A release's provenance bundle is attached as zig-runner.provenance.intoto.jsonl (was zig-runner.provenance.jsonl), the name OpenSSF Scorecard's Signed-Releases check recognises
- CI runs eviction's really-full-disk test (full-disk-test.sh) on every pull request that can affect the tests, instead of only by hand
- docs/native-backend.md says the published ports' 127/8 exemption assumes route_localnet is off on the host's bridges, and what a container can send when it is on
- A native job container's --tmpfs is refused above the runner's mounts and files (/github, /etc) and where an image's symlink leads onto them, and with no job cgroup (RUNNER_NATIVE_CGROUP=none) an unsized --tmpfs or more than 1 GiB of --tmpfs and --shm-size fails the job
Fixed
- Native backend: a job container's host-path volumes are refused on a mount without nosuid and nodev on the host, nested with another volume, on a rootless runner, or on a filesystem that cannot be idmapped unless RUNNER_NATIVE_HOST_PATHS_UNMAPPED=true; mount points are made before any bind and through no mount; each job is held to the list checked at start; the runner's executable, App key directory and env files are refused; and a refused source says nothing of what exists on the host
- Native backend: a job container's explicit host ports must be inside RUNNER_NATIVE_PORT_RANGE (49152-65535 by default) and never the runner's own database, engine or Docker port; ports published beyond loopback refuse peers of the runner's jobs and of the host's bridges; 127.0.0.1 is published on ::1 too; the relay connects without blocking, closes idle connections, lets a newcomer replace the quietest and caps new UDP clients a second
- Native backend: a job container's named volumes are refused for a job the runner does not vouch for and for a fork's pull request, an open waits for another job's filling of one only so long and not past a cancel, and the filling is stopped after 10 minutes or at the job's cancel
- Named volumes: a fork's cache growth no longer evicts a repository's named volume ahead of untrusted ones; repository-scope volumes rank a week later, as images rank an hour later (#428)
- Published ports (#428): the relay also refuses peers reached through a veth into another namespace or a tun/tap device (routed pods, VMs), keeps a UDP client's verdict for a second instead of reading the routes per datagram, and counts an engine or Docker host written without tcp:// as reserved; the range refusal names RUNNER_NATIVE_PORT_RANGE=1024-65535 for Docker's behaviour.
- A native job's Zig cache seed that an eviction took just as it was built is built again (up to three times) instead of leaving the job unseeded
- Eviction no longer overruns a job's end on a chain of directories with no files: a walk of a volume asks the clock at every directory, and a directory too deep to name is not walked a component at a time. A volume too big to walk in one pass is counted over several, resuming from the directories the last one left in its size file. A dead tree that will not go is left alone for an hour instead of costing a syncfs at every job's end (#357).
- The end to end suite reads a cancelled job's steps only once each has its conclusion, not when GitHub's step list still lags behind the finished job (#437)
- A host path (RUNNER_NATIVE_HOST_PATHS) that the runner's mount table also shows at another path without nosuid and nodev is refused at start and closed for each job; the docs say to mount the whole filesystem nosuid,nodev, since a self-bind covers only the runner's mount namespace and is lost at reboot
- A docker:// step that never runs (an if: that is false whatever the job's state) no longer decides whether the job runs natively: its image is not pulled, so one that cannot be pulled no longer moves the whole job to Dagger (#351)