All checks were successful
check / check (push) Successful in 1m9s
Add a root Dockerfile.lint that carries the checks as build steps -- a `lint` stage running `hugo --minify --printPathWarnings` and a `fmt-check` stage running the prettier check -- and reduce script/lint and script/fmt-check to building their stage. A successful build is a clean check. There is no host path and deliberately no "am I already inside a container?" branch, which would be a host lint path in disguise. The two stages share a `base` whose first four instructions are byte-identical to the main Dockerfile's, so the expensive `RUN script/bootstrap` layer that compiles the pinned Hugo from source is a cache hit against the main image instead of a second build of the same thing. Resolve the resulting recursion by splitting the checks by where they run, not with an escape hatch. `make check` runs script/lint, so the main Dockerfile can no longer `RUN make check`: that would be docker-in-docker inside a bare Alpine with no docker client and no daemon socket, and script/cibuild is what CI runs on every push. The main Dockerfile therefore runs `make test`, the production build, and script/cibuild builds it and then calls script/lint and script/fmt-check. CI still covers the production build, lint and the format check, and it runs exactly what a developer runs. script/fmt stays on the host because it rewrites the working tree, which a container build cannot do. That makes it the authoritative copy of the prettier version, scope and flags that the fmt-check stage duplicates; both sides carry a keep-in-sync note. The duplication is forced: any `RUN script/fmt-check` inside the image is the recursion again. Caching is waived for the checks in the shape this repo already settled: `ARG CHECK_EPOCH` with no default, declared and guarded separately in each stage because ARG does not cross a FROM, with the value expanded into the checked command as well as the guard so invalidation does not rest on BuildKit's treatment of an unreferenced ARG. All four image-building entrypoints now generate and pass it -- script/cibuild, script/docker, script/lint, script/fmt-check. Verified: two consecutive script/lint runs on an unchanged tree both executed hugo for real, with script/bootstrap CACHED; a constant-epoch counterfactual restored the false green (exit 0, lint layer CACHED, no hugo output); an empty epoch failed closed on the guard; a broken template failed the lint stage and an unformatted README failed the fmt-check stage, both reverted and re-run clean; script/cibuild and `make check` are green with all three checks demonstrably executing.
56 lines
2.7 KiB
Docker
56 lines
2.7 KiB
Docker
# Hugo static-site build image. The build runs `make test` -- a clean
|
|
# `hugo --minify` production build -- so the image build fails on any
|
|
# template, content or config error.
|
|
#
|
|
# It deliberately does NOT run `make check`. `make check` runs
|
|
# script/lint, and script/lint is a `docker build` of Dockerfile.lint,
|
|
# so `RUN make check` here would be docker-in-docker inside a bare
|
|
# alpine with no docker client and no daemon socket. The checks are
|
|
# therefore split by where they run: the production build here, lint and
|
|
# the prettier format check in Dockerfile.lint. Do not reintroduce a
|
|
# `make check` (or a `make lint` / `make fmt-check`) line in this file.
|
|
#
|
|
# CI coverage is unaffected: script/cibuild builds this image and then
|
|
# calls script/lint and script/fmt-check, so every check in `make check`
|
|
# still runs on every push -- see script/cibuild.
|
|
#
|
|
# Build this only via script/cibuild or script/docker: both pass the
|
|
# CHECK_EPOCH build argument that this file requires, and a bare
|
|
# `docker build .` fails by design. See the guard below for why.
|
|
# alpine 3.21, 2026-02-28
|
|
FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
|
|
|
WORKDIR /src
|
|
|
|
# Install build dependencies first so the layer caches until the
|
|
# scripts change (script/bootstrap installs git, make, go, hugo,
|
|
# node/npm). Hugo is not an apk package here: script/bootstrap builds
|
|
# the exact pinned version with `go install`, hash-verified against
|
|
# sum.golang.org, so the published artifact does not depend on whatever
|
|
# hugo this base image's repos happen to serve.
|
|
COPY script/ script/
|
|
RUN script/bootstrap
|
|
|
|
COPY . .
|
|
|
|
# CHECK_EPOCH is a per-invocation nonce supplied by script/cibuild and
|
|
# script/docker. Without it an unchanged tree serves this layer from
|
|
# cache and the build reports a green it never ran. ARG is stage-scoped,
|
|
# so it must be redeclared in every stage that runs checks - this image
|
|
# has one stage, so one declaration. Declared with no default: a default
|
|
# would be a constant, and a constant is a stable cache key. The guard
|
|
# makes a bare `docker build .` fail loudly instead of silently reusing
|
|
# the empty (and therefore stable) cache key. Expand the value into the
|
|
# command so the cache miss does not depend on BuildKit's handling of an
|
|
# unreferenced ARG. Both the guard and the check RUN reference the value,
|
|
# so both are value-keyed: there are two independent invalidation points
|
|
# here, not one. Keep both.
|
|
#
|
|
# Everything above this point still caches, so the script/bootstrap
|
|
# layer - which compiles Hugo from source - is not rebuilt.
|
|
ARG CHECK_EPOCH
|
|
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
|
|
|
# Run the production build - build fails if the site does not build.
|
|
RUN echo "check epoch: ${CHECK_EPOCH}" && make test
|