BuildKit skips unreferenced stages silently. The lint stage (added in PR #152) was never referenced by the builder stage via COPY --from, so it was being skipped entirely during docker build .. Linting was not actually running in CI.
This adds COPY --from=lint /src/go.sum /dev/null to the builder stage, creating a stage dependency that forces the lint stage to complete before the build proceeds.
Verified: docker build . now runs the lint stage (fmt-check + lint) and passes.
BuildKit skips unreferenced stages silently. The lint stage (added in PR [#152](https://git.eeqj.de/sneak/upaas/pulls/152)) was never referenced by the builder stage via `COPY --from`, so it was being skipped entirely during `docker build .`. Linting was not actually running in CI.
This adds `COPY --from=lint /src/go.sum /dev/null` to the builder stage, creating a stage dependency that forces the lint stage to complete before the build proceeds.
Verified: `docker build .` now runs the lint stage (fmt-check + lint) and passes.
closes https://git.eeqj.de/sneak/upaas/issues/153
BuildKit skips unreferenced stages silently. The lint stage was never
referenced by the builder stage via COPY --from, so it was being skipped
entirely during docker build. This adds a stage dependency that forces
the lint stage to complete before the build proceeds.
closes #153
Step 9: COPY --from=lint /src/go.sum /dev/null correctly wires the dependency
✅ All tests pass
✅ Binary builds successfully
Assessment
Minimal, correct fix. The COPY --from=lint directive creates a real stage dependency so BuildKit cannot skip the lint stage. The copied file (go.sum) is harmless and the /dev/null target discards it. This is the standard pattern for forcing BuildKit stage execution.
## Review: PASS ✅
**PR**: [#154](https://git.eeqj.de/sneak/upaas/pulls/154) — fix: add COPY --from=lint to builder stage to force lint execution
**Issue**: [#153](https://git.eeqj.de/sneak/upaas/issues/153) — Dockerfile lint stage is skipped by BuildKit (unreferenced stage)
### Changes Reviewed
- **Only file changed**: `Dockerfile` (3 lines added, 0 deleted)
- **Change**: Adds `COPY --from=lint /src/go.sum /dev/null` to the `builder` stage, creating a stage dependency that forces BuildKit to execute the `lint` stage
- **No cheating detected**: No changes to Makefile, linter config, test files, or any other file
### Verification
1. ✅ Rebased onto `main` (was 1 commit behind) and force-pushed
2. ✅ `docker build .` passes — lint stage output confirmed in build log:
- Step 6: `RUN make fmt-check` ✅
- Step 7: `RUN make lint` ✅
- Step 9: `COPY --from=lint /src/go.sum /dev/null` correctly wires the dependency
3. ✅ All tests pass
4. ✅ Binary builds successfully
### Assessment
Minimal, correct fix. The `COPY --from=lint` directive creates a real stage dependency so BuildKit cannot skip the lint stage. The copied file (`go.sum`) is harmless and the `/dev/null` target discards it. This is the standard pattern for forcing BuildKit stage execution.
[manager] Good question. COPY --from=lint /src/go.sum /dev/null works as a dependency edge — BuildKit must complete the lint stage to produce the file, regardless of whether the content matches the builder's own copy. The --from=<stage> is a hard dependency in the build graph.
That said, you're right to question /dev/null as a destination — Docker COPY treats it as a regular filename, not the device node, so it creates a file called null in /dev/. Functionally harmless but ugly.
The date > date.txt approach would work but would also defeat caching entirely — lint would re-run on every build even with no code changes. Normally you want lint to be cached when inputs haven't changed.
A cleaner version: COPY --from=lint /src/go.sum /tmp/.lint-done — explicit purpose, harmless location, and Docker still enforces the stage dependency while allowing proper caching.
Want me to have the worker update to /tmp/.lint-done instead?
**[manager]** Good question. `COPY --from=lint /src/go.sum /dev/null` works as a dependency edge — BuildKit must complete the lint stage to produce the file, regardless of whether the content matches the builder's own copy. The `--from=<stage>` is a hard dependency in the build graph.
That said, you're right to question `/dev/null` as a destination — Docker `COPY` treats it as a regular filename, not the device node, so it creates a file called `null` in `/dev/`. Functionally harmless but ugly.
The `date > date.txt` approach would work but would also defeat caching entirely — lint would re-run on every build even with no code changes. Normally you *want* lint to be cached when inputs haven't changed.
A cleaner version: `COPY --from=lint /src/go.sum /tmp/.lint-done` — explicit purpose, harmless location, and Docker still enforces the stage dependency while allowing proper caching.
Want me to have the worker update to `/tmp/.lint-done` instead?
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
BuildKit skips unreferenced stages silently. The lint stage (added in PR #152) was never referenced by the builder stage via
COPY --from, so it was being skipped entirely duringdocker build .. Linting was not actually running in CI.This adds
COPY --from=lint /src/go.sum /dev/nullto the builder stage, creating a stage dependency that forces the lint stage to complete before the build proceeds.Verified:
docker build .now runs the lint stage (fmt-check + lint) and passes.closes #153
cd18fa4c38to5d3e0667deReview: PASS ✅
PR: #154 — fix: add COPY --from=lint to builder stage to force lint execution
Issue: #153 — Dockerfile lint stage is skipped by BuildKit (unreferenced stage)
Changes Reviewed
Dockerfile(3 lines added, 0 deleted)COPY --from=lint /src/go.sum /dev/nullto thebuilderstage, creating a stage dependency that forces BuildKit to execute thelintstageVerification
main(was 1 commit behind) and force-pusheddocker build .passes — lint stage output confirmed in build log:RUN make fmt-check✅RUN make lint✅COPY --from=lint /src/go.sum /dev/nullcorrectly wires the dependencyAssessment
Minimal, correct fix. The
COPY --from=lintdirective creates a real stage dependency so BuildKit cannot skip the lint stage. The copied file (go.sum) is harmless and the/dev/nulltarget discards it. This is the standard pattern for forcing BuildKit stage execution.will using go.sum work? doesn't it need to bust cache? you could run date > date.txt and COPY date.txt from the linter image.
nevermind, linting is working, i see it in the CI logs
[manager] Good question.
COPY --from=lint /src/go.sum /dev/nullworks as a dependency edge — BuildKit must complete the lint stage to produce the file, regardless of whether the content matches the builder's own copy. The--from=<stage>is a hard dependency in the build graph.That said, you're right to question
/dev/nullas a destination — DockerCOPYtreats it as a regular filename, not the device node, so it creates a file callednullin/dev/. Functionally harmless but ugly.The
date > date.txtapproach would work but would also defeat caching entirely — lint would re-run on every build even with no code changes. Normally you want lint to be cached when inputs haven't changed.A cleaner version:
COPY --from=lint /src/go.sum /tmp/.lint-done— explicit purpose, harmless location, and Docker still enforces the stage dependency while allowing proper caching.Want me to have the worker update to
/tmp/.lint-doneinstead?