-
Notifications
You must be signed in to change notification settings - Fork 8
feat: retain runner caches and add build-cache profiles #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
f95940c
chore(ai-review): record shared cache design forecast
elibosley 923de11
feat: add opt-in registry build-cache profiles
elibosley dea3bc9
fix: clarify cache rollout and preserve settings rows
elibosley ff41baa
chore(ai-review): record final review receipt
elibosley f9adf7a
test: expose nested Docker integration failures
elibosley b9d5e74
test: keep cache binds outside the DinD tmpfs
elibosley acad205
chore: mark shell scripts executable
elibosley a39fbea
fix(ci): validate documentation links against the candidate tree
elibosley 08c160a
chore(ai-review): refresh lint repair receipt
elibosley 6ab6336
fix(ci): avoid autolinking the URL match expression
elibosley b078063
chore(ai-review): refresh lint repair receipt
elibosley d7b431a
test: fail explicitly when builder volumes are shared
elibosley 2f7f3a9
chore(ai-review): record volume assertion review
elibosley 106b47b
fix(cache): retain runner data across stop and restart
elibosley e4400e5
docs(cache): clarify retained and retired cache lifecycles
elibosley 52c9803
chore(ai-review): record cache retention review
elibosley 3082e96
test(cache): verify authenticated registry sharing on dispatch
elibosley 3541a35
chore(ai-review): record registry proof review
elibosley ec0653e
docs(cache): require observed registry visibility before activation
elibosley 72619fb
chore(ai-review): record registry visibility review
elibosley 0ac920c
fix(security): bound retained cache and registry proof trust
elibosley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
.limetech/ai-review-markers/feat-shared-build-cache-8c24ca91be08.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "disposition": "PATCH", | ||
| "forecast_commit": "c214cf26f7868ce28f4f7db38bb9993264e3f764", | ||
| "reviewed_sha": "8b5ad92f18df6276a0dc4562f6cfbd033259f5cb", | ||
| "schema": "limetech.ai-review-marker.v2", | ||
| "stage": "final", | ||
| "unresolved_proportionality_findings": [] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| # Share build caches through an existing registry | ||
|
|
||
| The optional **Build cache profile** lets CI jobs reuse exported Docker build | ||
| layers across runners. Each slot keeps its own Docker daemon and writable data. | ||
| The plugin supplies configuration, not a registry server or registry credentials. | ||
| The existing pull-through image mirror remains separate and cannot accept cache | ||
| exports. | ||
|
|
||
| ## Configure the farm | ||
|
|
||
| 1. Open **Settings → CI Runner Farm → Settings → Build cache profile**. | ||
| 2. Select **Existing registry (workflow opt-in)**. | ||
| 3. Enter a tagless repository, such as `registry.example.com/team/build-cache`. | ||
| 4. Set **Local cache budget per builder (GiB)**. The default is 20 GiB. | ||
| 5. Apply the settings. In classic mode, wait for runners to finish their jobs and adopt the new profile. | ||
| 6. For named pools, schedule a **Fleet Restart** after active jobs finish. Apply alone does not replace those runners. | ||
| 7. Wait for the replacement runners before updating build workflows as described below. | ||
|
|
||
| The configuration keys are `BUILD_CACHE_MODE` (`off` or `registry`), | ||
| `BUILD_CACHE_REPOSITORY`, and `BUILD_CACHE_LOCAL_GIB` (1–1024). | ||
| Off preserves existing behavior, including runner configuration fingerprints. | ||
| Registry mode requires Docker-in-Docker. Host-socket mode is not supported. | ||
|
|
||
| Each GitHub runner and GitLab Docker-executor job receives two read-only files: | ||
|
|
||
| - `/etc/ci-runner-farm/build-cache/profile.env`: the repository and config path. | ||
| - `/etc/ci-runner-farm/build-cache/buildkitd.toml`: the OCI worker's GC budget. | ||
|
|
||
| Profiles are immutable snapshots. Changes do not rewrite files mounted by busy | ||
| jobs. Old snapshots remain in the plugin's runtime directory until reboot. | ||
| The current snapshot is generated again during runner provisioning. | ||
|
|
||
| ## Use the profile in a workflow | ||
|
|
||
| Use a current Buildx client and BuildKit version that supports `maxUsedSpace`. | ||
| This profile targets the OCI worker in a `docker-container` builder. It does not | ||
| configure arbitrary existing builders, remote builders, or `docker build` calls. | ||
| GitLab job images must include the Docker CLI and Buildx plugin. | ||
|
|
||
| Authenticate to the cache registry inside the job, using a credential with only | ||
| the required repository permissions. The plugin does not expose its host PAT, | ||
| runner token, or image-pull credentials through this profile. Configure private | ||
| CA trust in the job/builder when required. The profile does not disable TLS or | ||
| relax the farm's network firewall. A registry blocked by strict isolation remains | ||
| blocked. | ||
|
|
||
| This shell example works in a GitHub shell job or GitLab job. Set | ||
| `CACHE_SCOPE` from a stable hash of the full project, build target, platform, and | ||
| branch identity. Use the same scope on different runners to reuse their cache. | ||
| Keep output image references separate from cache references. | ||
|
|
||
| ```bash | ||
| set -euo pipefail | ||
| . /etc/ci-runner-farm/build-cache/profile.env | ||
| : "${CACHE_SCOPE:?Set a project/target/platform/branch-specific cache scope}" | ||
| [[ "$CACHE_SCOPE" =~ ^[a-z0-9][a-z0-9_.-]{0,100}$ ]] || exit 1 | ||
| cache_ref="${CRF_BUILD_CACHE_REPOSITORY}:${CACHE_SCOPE}" | ||
| builder="crf-$(cat /proc/sys/kernel/random/uuid)" | ||
| docker buildx create --name "$builder" --driver docker-container \ | ||
| --buildkitd-config "$CRF_BUILDKIT_CONFIG" --bootstrap | ||
| trap 'docker buildx rm "$builder"' EXIT | ||
| docker buildx build --builder "$builder" \ | ||
| --cache-from "type=registry,ref=$cache_ref" \ | ||
| --cache-to "type=registry,ref=$cache_ref,mode=max,image-manifest=true" \ | ||
| --load -t example/app:ci . | ||
| ``` | ||
|
|
||
| The example removes only its own builder after the job. Registry cache survives | ||
| that cleanup. A workflow may retain its own builder instead, but it must recreate | ||
| that builder when the profile changes so the new GC budget takes effect. | ||
| GitHub jobs using `container:` must also mount the profile directory read-only | ||
| into their job container. Shell jobs receive it directly. | ||
|
|
||
| For GitHub's Docker actions, load `profile.env` in a shell step and write the | ||
| needed values to `$GITHUB_OUTPUT`. Pass the config path to | ||
| `docker/setup-buildx-action` as `buildkitd-config`. Pass explicitly scoped refs to | ||
| `docker/build-push-action` as `cache-from` and `cache-to`. Do not assume that the | ||
| runner's environment automatically appears in GitHub's expression `env` context. | ||
| See Docker's [builder configuration](https://docs.docker.com/build/ci/github-actions/configure-builder/) | ||
| and [registry cache examples](https://docs.docker.com/build/ci/github-actions/cache/). | ||
|
|
||
| ## Scope, failure, and storage limits | ||
|
|
||
| Cache names are not an access-control boundary. Only trusted jobs should share | ||
| a writable cache repository. Separate projects and trust levels with registry | ||
| permissions and separate repositories. Never give untrusted pull requests a | ||
| credential that can overwrite trusted caches. Branch-specific tags avoid normal | ||
| collisions but do not stop a credential holder from choosing another tag. | ||
| Serialize exports to the same ref, or use separate refs for concurrent writers. | ||
| Use BuildKit secret mounts for secrets; never bake credentials into layers. | ||
|
|
||
| The profile provides no implicit `latest` cache tag. A missing import cache is a | ||
| normal cold build under Docker's behavior. Export failures remain job failures | ||
| unless the workflow deliberately changes Docker's default error handling. Do not | ||
| add `ignore-error=true` or fall back to an unconfigured builder to hide failures. | ||
|
|
||
| The local budget is a garbage-collection target, not a hard quota. Active build | ||
| data can exceed it. It does not cover Docker images, other builders, workspaces, | ||
| package caches, or the image mirror. Exported caches also need registry retention | ||
| and garbage collection, controlled by the registry owner. Reducing local budgets | ||
| trades disk use for cache downloads and depends on registry availability. | ||
|
|
||
| Enabling this option does not prune old builders or immediately recover their | ||
| disk space. Migrate workflows first, then retire unused builders through their | ||
| normal owner. Remove workflow references to the profile before disabling the | ||
| option. Classic fleets remove the mount as runners drain and are replaced. | ||
| Named pools require the same scheduled Fleet Restart described above. | ||
|
|
||
| ## Keep caches across Stop and Restart | ||
|
|
||
| **Stop** and **Restart** retain per-slot Docker data, GitLab job caches, and the | ||
| shared image mirror cache. A later Start reuses the retained data for the same | ||
| slot and cache root. This applies with the build cache profile on or off. | ||
| The plugin records the provider and baked runner configuration identity for each | ||
| retained slot. If that identity changes, including a provider, owner, project, | ||
| registry, or trust-scope change, the plugin purges that slot's retained Docker, | ||
| workspace, job-cache, socket, and log data before starting it again. The shared | ||
| image mirror remains separate and is not treated as per-slot job state. | ||
| The plugin still removes runner containers and performs provider credential | ||
| cleanup. Plugin uninstall and active GitLab runner-token removal also retain | ||
| caches because they use Stop. | ||
|
|
||
| Stop does not guarantee that active jobs finish. Schedule maintenance before | ||
| using Stop or Restart. Manual scale-down, autoscale-down, and permanent slot | ||
| retirement still delete that slot's Docker data and GitLab job cache. Registry | ||
| exports are unaffected. | ||
|
|
||
| To delete retained local caches, stop the fleet, then run the explicit command: | ||
|
|
||
| ```bash | ||
| /usr/local/emhttp/plugins/ci-runner-farm/include/runner-farm.sh prune-cache | ||
| ``` | ||
|
|
||
| This command deletes plugin-owned cache directories under the configured cache | ||
| root. It refuses to proceed while managed runners, sidecars, or job containers | ||
| remain. Cache retention does not reduce existing disk use. | ||
|
|
||
| ## Verification | ||
|
|
||
| `bash tests/build-cache.sh` checks profile validation, snapshots, fingerprints, | ||
| and both providers' mount contracts without external services. | ||
| `bash tests/cache-retention.sh` checks Stop/Restart retention and explicit cache | ||
| deletion for both providers using disposable on-disk fixtures. | ||
| `bash tests/build-cache-integration.sh` uses disposable local Docker builders and | ||
| a test registry to prove cross-builder cache reuse and effective GC settings. | ||
| It does not contact a farm host or modify existing runners. | ||
|
|
||
| ### Verify an authenticated registry | ||
|
|
||
| The **Lint** workflow has a manual **Also verify authenticated GHCR cache reuse** | ||
| option. The package-writing job accepts dispatches only from the repository's | ||
| `main` branch. Pull-request events and other refs cannot start it. Review the | ||
| exact `main` commit before dispatching. GitHub assigns its runner. | ||
|
|
||
| The job uses its short-lived `GITHUB_TOKEN` with `packages: write`, not a farm | ||
| credential. It exports synthetic Busybox layers to | ||
| `ghcr.io/<owner>/<repository>-build-cache:proof-<run-id>-<attempt>`. | ||
| Two fresh builders must have different local volumes, and the second must reuse | ||
| the exported `RUN` layer. Authentication or export errors fail the test. | ||
| The test never substitutes a local registry when the external registry fails. | ||
|
|
||
| Inspect the package's actual visibility, repository link, and workflow access | ||
| after the run. Do not assume that it is private. This repository's initial | ||
| synthetic proof produced a public package. Never use a public proof repository | ||
| for private build caches. A private production cache needs its own authorized | ||
| repository and access policy before the farm points to it. | ||
|
|
||
| The job removes its temporary Docker credentials and local test resources. It | ||
| leaves the small proof tag for inspection; the package owner controls retention. | ||
| A successful proof against a public package establishes authenticated exports | ||
| and cache reuse, but does not establish private-package read authorization. | ||
|
|
||
| For another registry, authenticate through a temporary, job-owned Docker config. | ||
| Set `CRF_CACHE_TEST_REPOSITORY` to its tagless repository and | ||
| `CRF_CACHE_TEST_TAG` to a unique tag starting with `proof-`. Then run | ||
| `bash tests/build-cache-integration.sh`. Both inputs are required together. | ||
| Do not use the farm's host PAT, runner token, or image-pull credential. | ||
|
|
||
| This check uses the plugin's generated profile in an isolated fixture. It does | ||
| not prove that a deployed runner has adopted its profile or that a production | ||
| workflow uses it. Verify the deployed read-only mount separately, then configure | ||
| each authorized build workflow as described above. Enabling the farm option | ||
| alone does not change existing build commands or reduce their old cache data. | ||
|
|
||
| On macOS, use `bash tests/run-linux-checks.sh` with Docker running and Homebrew | ||
| Coreutils installed (`brew install coreutils`). The wrapper runs the shell suite | ||
| in Linux. Its real-Docker integration stage uses the installed GNU tools on the | ||
| host because the production snapshot generator requires GNU `mv -T`. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: unraid/ci-runner-farm
Length of output: 9830
🏁 Script executed:
Repository: unraid/ci-runner-farm
Length of output: 24754
Declare the Bash requirement for GitLab job images.
The
bashfence does not select the interpreter. If GitLab runs this block withsh,[[ ... =~ ... ]]andset -o pipefailare unsupported. Require Bash or rewrite the example for POSIXsh.🤖 Prompt for AI Agents