Pipeline optimization: single-stage Dockerfile + gem cache #188
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "185-pipeline-optimization"
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
Cut Kaniko build time by eliminating the multi-stage runtime rootfs unpack (~1m30s) and adding persistent gem cache for CI (~60-80s). Restructure DORA docs into a directory for future expansion.
Closes #187
Changes
ruby-rails-build. Eliminates the secondFROM ruby-rails-runtimestage and its 1m30s rootfs unpack. Non-rootrailsuser created inline. Image is ~200MB larger (includes build tools) but builds faster.bundle-installstep now copies gems to/from a PVC-backed/cache/bundlevolume. On warm cache, bundle install drops from ~90s to ~10s. PVC creation instructions in inline comment.Test Plan
2>/dev/null || true)Review Checklist
Related Notes
docs/dora/pipeline-timing.md— timing data and optimization tablePR #188 Review
DOMAIN REVIEW
Tech stack: Dockerfile (container build), Woodpecker CI (k8s backend), Markdown docs.
Dockerfile (single-stage conversion)
The conversion from multi-stage to single-stage is sound. The key changes:
AS buildalias and the secondFROM ruby-rails-runtimestageCOPY --from=buildlayers that triggered a full rootfs unpack in Kanikorailsuser inline withgroupadd/useradd(previously provided by the runtime base image)USER 1000:1000explicitly -- good security practiceThe tradeoff (build tools remain in the final image, ~200MB larger) is properly documented in the inline comment. This is an acceptable tradeoff for a private deployment where image size is less critical than build speed.
One observation: the
chown -R rails:rails db log storage tmpruns as root before theUSERswitch, which is correct. The order of operations is sound.Woodpecker gem cache
The
backend_options.kubernetes.volumeMountssyntax is correct for Woodpecker's k8s backend. The inline PVC creation instructions and theWOODPECKER_BACKEND_K8S_VOLUMESserver config reference are accurate and helpful.The volume name
bundle-cacheis consistent between thevolumeMountsdeclaration and the server config JSON in the comments..dockerignore: Already contains
/vendor/bundle(line 54), which prevents CI-installed gems from being copied into the Docker build context. This is important and unchanged -- no issue here.BLOCKERS
1. Gem cache write step lacks graceful fallback
The read step (
cp -a /cache/bundle/. vendor/bundle/) correctly degrades with2>/dev/null || truewhen the PVC is absent. However, the write step (cp -a vendor/bundle/. /cache/bundle/) has no such guard. If the PVC is not mounted:/cache/bundle/won't exist as a directorycp -awill fail with a non-zero exit codeThis breaks the stated design goal: "Gem cache step is no-op until PVC is created (graceful fallback)."
Fix: add
2>/dev/null || trueto the write step as well:NITS
1. Branch naming mismatch (minor SOP deviation)
Branch is
185-pipeline-optimizationbut the parent issue is #187. Issue #185 is the closed DORA metrics analysis issue. The branch should reference the issue it closes. This is not blocking since the PR body correctly statesCloses #187, but it creates confusion in branch-to-issue traceability.2. docs/dora/ directory has only one file
Renaming
docs/dora.mdtodocs/dora/pipeline-timing.mdis forward-looking (the PR body says "DORA is broader than pipeline speed -- restructure into a directory"). However, a single-file directory looks premature. Consider adding adocs/dora/README.mdor index file. Non-blocking -- this is a structural preference.3. Timing table accuracy
The updated build-and-push row shows
~3m50s (single-stage, one unpack) / ~1m30s (fully cached). The PR summary claims "~1m30s savings" from eliminating the second rootfs unpack. These numbers are consistent, but the "fully cached" scenario (~1m30s) represents a separate optimization (Kaniko layer cache hit) that is not part of this PR. Consider clarifying that the~1m30sfigure is aspirational/pre-existing, not delivered by this change. Non-blocking.SOP COMPLIANCE
185-pipeline-optimizationbut parent issue is #187Closes #187present in PR bodyfrom_secret, no plaintext secretsPROCESS OBSERVATIONS
ReadWriteOnceaccess mode on the PVC means concurrent pipeline runs on different nodes will fail to mount the volume. For a single-node setup this is fine, but worth noting if the cluster scales.ReadWriteManywould be needed for multi-node, but requires a different storage class.VERDICT: NOT APPROVED
One blocker: the gem cache write step will break the pipeline when the PVC is absent. Add the
2>/dev/null || truefallback to the write-sidecpcommand to match the read-side behavior. After that fix, this is ready to merge.PR #188 Review (Re-review)
DOMAIN REVIEW
Tech stack: Dockerfile, Woodpecker CI (Kubernetes backend), Markdown docs. Applying container security, CI/CD pipeline, and k8s volume patterns.
Dockerfile (single-stage refactor)
FROM ruby-rails-runtimestage and itsCOPY --from=buildrootfs unpack. Sound optimization -- the prior two-stage pattern doubled the image unpack work in Kaniko.groupadd/useraddwith explicit UID/GID 1000.USER 1000:1000set before ENTRYPOINT. Correct security posture.chown -R rails:rails db log storage tmpensures the app directories are writable by the non-root user. Correct.AS buildalias removed since there is no second stage to reference it. Clean.Woodpecker CI (.woodpecker.yaml)
backend_options.kubernetes.volumeMountsreferencing/cache/bundle. Standard Woodpecker k8s volume pattern.cp -a /cache/bundle/. vendor/bundle/ 2>/dev/null || true-- graceful fallback when PVC is empty or missing. Correct.cp -a vendor/bundle/. /cache/bundle/ 2>/dev/null || true-- previous blocker is fixed. The2>/dev/null || truefallback is now present on both sides.ReadWriteOnceaccess mode is appropriate for a single pipeline runner. If pipelines run in parallel on different nodes, this would needReadWriteMany, but that is a future concern and documented implicitly by the access mode choice.Docs rename (dora.md to dora/pipeline-timing.md)
similarity index 95%. Only content changes are to the optimization table, updating rows to reflect implemented optimizations vs. planned ones.BLOCKERS
None. The previous blocker (missing
2>/dev/null || trueon the write-sidecp) is resolved.NITS
Branch name mismatch: Branch is
185-pipeline-optimizationbut the parent issue is #187. Convention is{issue-number}-{kebab-case-purpose}, so the branch should be187-pipeline-optimization. Not blocking since the PR body correctly referencesCloses #187, but worth noting for traceability.PVC access mode: The inline comment specifies
ReadWriteOnce. If parallel pipelines are ever needed across nodes, this would need to becomeReadWriteMany(or use a cache-per-node strategy). A brief note in the comment about this constraint would help future operators.Volume name coupling: The
volumeMountsreferencename: bundle-cachewhich must match the volume name configured inWOODPECKER_BACKEND_K8S_VOLUMES. The inline comment shows the PVC name aswoodpecker-bundle-cacheand the volume name in the JSON asbundle-cache. This mapping is correct but the indirection could confuse someone -- consider adding a note that thenamefield involumeMountsmust match thenamein the server-side volume config, not the PVC name.SOP COMPLIANCE
185-pipeline-optimizationreferences #185, not parent issue #187).dockerignoreexcludes.env*, credential keys; no hardcoded secrets in diff)PROCESS OBSERVATIONS
docs/dora/sets up a clean directory for future DORA content (deployment frequency, change failure rate, etc.).VERDICT: APPROVED
Previous blocker is resolved. All three changes are clean, well-documented, and correctly scoped. The branch naming mismatch is a nit, not a blocker.