Run #4267 finally got the bind mount through (Merged Binds includes
/root/.docker:/root/.docker:ro), but docker build then died:
failed to update builder last activity time:
open /root/.docker/buildx/activity/.tmp-...: read-only file system
The catthehacker job container uses buildx, which writes activity
tracking to /root/.docker/buildx/. Mounting the whole host /root/.docker
read-only made that path read-only too.
Right scope is the file, not the dir:
-v /root/.docker/config.json:/root/.docker/config.json:ro
That gives the cli the registry auth it needs while leaving the rest
of /root/.docker on the container's writable overlay so buildx can
populate its own activity dir without colliding with the host's. Also
matches the principle of mounting the minimum the secret requires.
valid_volumes entry updated to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4266 dropped the /root/.docker bind silently:
Custom container.HostConfig from options ==> &{Binds:[/root/.docker:/root/.docker:ro]…}
[/root/.docker] is not a valid volume, will be ignored
Merged container.HostConfig ==> &{Binds:[/var/run/docker.sock:/var/run/docker.sock /root/.docker:/root/.docker:ro]…}
no basic auth credentials
Wait, the merged binds list does include /root/.docker — but the line
between them, "[/root/.docker] is not a valid volume, will be ignored",
fires *during* the merge step's allowlist check, and the bind ends up
absent in the actual container start (the `Binds:` list shown is
pre-filter). Net result: the registry creds are not in the job
container, push fails.
Root cause: container.valid_volumes is an allowlist of source-path
globs, not full bind specs. The entry
`/root/.docker:/root/.docker:ro` was being treated as a literal pattern
and never matched the bind's source `/root/.docker`. Same for the
other two entries — they were just no-ops because the auto-mount /
explicit options were the things actually creating the binds.
Fix: rewrite valid_volumes entries as bare source paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4265 (the first run after the config.yaml wiring fix actually took
effect) failed with:
failed to create container: 'Error response from daemon:
Duplicate mount point: /var/run/docker.sock'
act_runner v0.4.1 already auto-mounts /var/run/docker.sock into every
job container; listing it a second time in container.options is a
hard error on container create. Same likely applies to /cache, which
the workflow doesn't actually use anyway (the inner build.sh bind-
mounts via REPO_ROOT/BUILD_DIR, not /cache).
Trim container.options down to *only* the bind act_runner doesn't
provide: -v /root/.docker:/root/.docker:ro for registry credentials.
valid_volumes stays as the broader allowlist for workflow-requested
mounts but doesn't force the mounts itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The runner config.yaml on disk was decorative — never read. The upstream
gitea/act_runner image's run.sh only adds `--config <file>` when the
CONFIG_FILE env var is set, and our compose set neither CONFIG_FILE nor
mounted config.yaml into the container. So `timeout: 240m`,
`container.options`, `valid_volumes` etc. were silently ignored and the
runner ran on built-in defaults.
This is also why iter17's `-v /root/.docker:/root/.docker:ro` addition
to config.yaml had no effect on run #4264: the runner never read it.
The push still failed with "no basic auth credentials".
Fix: bind-mount ./config.yaml into the runner container at
/etc/act_runner/config.yaml and set CONFIG_FILE to that path. After a
`docker compose up -d --force-recreate`, the runner picks up everything
in config.yaml — including the per-job-container /root/.docker bind.
Per-job timeouts in build-iso-linux.yaml are set via `timeout-minutes:
240` at the job level, which overrides the daemon default anyway, so
nothing was visibly broken before. But silently-ignored config is a
trap for the next thing we add to config.yaml, so wire it correctly now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run #4263 cleared the new builder-image job's `docker build` step
cleanly but `docker push` died with:
no basic auth credentials
The runner host (10.0.0.51) is logged in to docker-registry.silverlabs.uk —
that's how iter1-15 builder images got pushed by hand. But the
silvermetal-builder act_runner only mounts /root/.docker into its own
container, not into the job containers it spawns. catthehacker/ubuntu:
act-latest runs as root and reads /root/.docker/config.json for auth;
without that file mounted in, docker-cli has no creds to send via the
DooD socket and the registry returns 401 Basic-realm.
Fix: extend the act_runner `container.options` to mount
/root/.docker:/root/.docker:ro into each job container, and add the same
entry to valid_volumes. Update the runner README so first-time deploys
know the host-side `docker login` is what makes the in-CI push work.
This requires a one-time runner redeploy on 10.0.0.51:
cd /opt/silvermetal-builder-runner
git pull
docker compose up -d --build
After that, the builder-image job pushes cleanly and feeds its digest
to build-and-verify as designed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
act_runner-based deployment that handles `runs-on: silvermetal-builder` jobs.
Adapted from the stinky-roger-tv flutter-builder pattern with three changes:
- privileged: true (live-build needs loop devices + chroot)
- 4h job timeout (covers two reproducibility-gated ISO builds + diffoscope)
- silvermetal-builder label maps to catthehacker/ubuntu:act-latest, not the
silvermetal-builder image — the builder image stays minimal (no docker-cli),
and build.sh invokes it via `docker run` from the catthehacker job shell
Deployed at /opt/silvermetal-builder-runner/ on the SLAB docker host
(10.0.0.51); registered with git.silverlabs.uk and reporting healthy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>