Cache bundle install in CI pipeline using host volume #81
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "60-cache-bundle-install"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds persistent bundle caching to the Woodpecker CI pipeline. On cache hit,
bundle installbecomes a no-op (~seconds instead of 2-3 minutes). Cache invalidates automatically when Gemfile.lock changes.Changes
.woodpecker.yaml— Addedrestore-bundle-cachestep that copies cached gems from a Kubernetes hostPath volume when the Gemfile.lock md5 checksum matches.woodpecker.yaml— Addedsave-bundle-cachestep that persistsvendor/bundleafter lint+test succeed, only writing when checksum differs.woodpecker.yaml— Modified lint and test steps to usebundle config set --local path vendor/bundleso both consume the restored cache.woodpecker.yaml— Updateddepends_ongraph so restore runs before lint/test, and save runs after both passTest Plan
Review Checklist
Related Notes
None — standalone CI optimization.
Related
Closes #60
QA Review
Finding: Parallel
bundle installrace condition on cache missSeverity: Medium
When the cache misses (first run or Gemfile.lock change), both
lintandteststeps run concurrently (bothdepends_on: [restore-bundle-cache]). Both executebundle install --jobs=4writing to the samevendor/bundledirectory simultaneously. Bundler is not designed for concurrent installs into the same path -- this can produce corrupted gem state or intermittent failures.On cache hit this is safe because
bundle installbecomes a no-op when all gems are already present.Fix: Either serialize lint before test (add
depends_on: [lint]to the test step, making lint the canonical installer), or add a dedicatedinstallstep that both lint and test depend on.Other observations
backend_options.kubernetes.volumeswithhost_pathandDirectoryOrCreateis correct for single-agent K8s setupmd5sum Gemfile.lockis simple and effectivecp -apreserves ownership/permissions which is needed for native extensionssave-bundle-cachestep correctly gates on both lint and testVERDICT: REQUEST_CHANGES
Fix the parallel install race condition on cache miss, then this is good to merge.
Fixed the parallel
bundle installrace condition. Pushed commit5c53eaf.What changed: Extracted
bundle installinto a dedicatedbundle-installstep that runs serially betweenrestore-bundle-cacheandlint/test. Both lint and test now depend onbundle-installinstead ofrestore-bundle-cache, so they run in parallel only after gems are fully installed.Pipeline flow:
restore-bundle-cache-- restore from host volume on checksum matchbundle-install-- singlebundle install(no-op on cache hit, full install on miss)lint+test-- run in parallel, gems already installedsave-bundle-cache-- persist to host volume after lint+test passbuild-and-push-- unchangedPR #81 Review
DOMAIN REVIEW
Tech stack: Woodpecker CI (YAML pipeline), Kubernetes backend (k8s hostPath volumes), Ruby/Bundler toolchain.
Workspace sharing model: Correct. Woodpecker k8s backend shares a workspace volume across all pipeline steps.
vendor/bundlepopulated inrestore-bundle-cacheandbundle-installpersists intolintandteststeps via the shared workspace -- no additional volume mount needed on those steps.Dependency graph analysis:
No deadlocks. No races in the dependency graph. Parallel lint/test share an immutable workspace (both only read
vendor/bundle, neither writes to it).Cache invalidation:
md5sum Gemfile.lock | cut -d' ' -f1is a correct invalidation key. When Gemfile.lock changes, checksum changes, full reinstall triggers, cache gets overwritten on success.build-and-push isolation: Kaniko uses its own Dockerfile multi-stage build (FROM ruby-rails-build, runs its own
bundle install). It does NOT consume the CIvendor/bundle. TheCOPY vendor/* ./vendor/line in the Dockerfile copies from the repo source (which is just a.keepor similar), not the CI-populated vendor/bundle. Confirmed safe.hostPath security:
/var/lib/woodpecker-cache/bundlewithtype: DirectoryOrCreateis appropriate for a single-node k3s cluster. The path is specific enough to avoid namespace collision. No privilege escalation risk since the steps run as the default container user and only read/write their own cache files.alpine:3.20 for cache steps:
md5sumandcp -aare busybox builtins -- no package install needed. Lightweight image choice for a copy-only step is correct.BLOCKERS
None.
This is a CI configuration change (infrastructure-only, no application code). The BLOCKER criteria (test coverage for new functionality, unvalidated user input, secrets, duplicated auth logic) do not apply here. There is no user-facing input, no secrets committed, and no auth logic involved.
WARNINGS
1. Concurrent pipeline cache corruption (LOW RISK)
The
save-bundle-cachestep does:If two pipelines overlap on the same node (e.g., rapid push + PR event), one pipeline's
restore-bundle-cachecould read a partially-written/cache/bundlefrom the other's save operation. On a single-agent cluster this is unlikely but not impossible.Mitigation options (not blocking -- just noting for future):
mvwith a temp directory instead ofrm -rf+cp -a(atomic rename)2.
BUNDLE_DEPLOYMENT: ""andBUNDLE_WITHOUT: ""These environment variables are set to empty strings in
bundle-install,lint, andteststeps. This works (it unsets the bundler config options that might be baked into the base image), but it would be clearer to document WHY these are needed -- the base image likely setsBUNDLE_DEPLOYMENT=1for production use.NITS
md5sum vs sha256sum: md5 is fine for cache invalidation (not a security context), but sha256sum would be a zero-cost upgrade that future-proofs against any tooling that flags md5 usage.
Missing
failure: ignoreconsideration: Ifsave-bundle-cachefails (disk full, permission error), it will mark the pipeline as failed even though lint+test passed. Consider whether cache-save failures should be non-fatal. This is a design choice, not a bug.Minor: event filter duplication: The
whenclause onrestore-bundle-cache,bundle-install, andsave-bundle-cacherepeats[pull_request, push]. This matches the top-levelwhenfilter, so it is technically redundant but harmless (defensive in case the top-level filter changes later).SOP COMPLIANCE
60-cache-bundle-installfollows{issue-number}-{kebab-case-purpose}conventionCloses #604c6e4f4 Fix quick-add form reset...) -- wait, that is a different commit. The PR diff shows the pipeline changes are the only content. PR title is descriptive.PROCESS OBSERVATIONS
bundle install(the previous behavior). Worst case if caching breaks: pipeline is slower, not broken.VERDICT: APPROVED