# Lint-only image. `script/lint` builds this file and nothing else: the # lint runs as a build step, so a successful build IS a clean lint. # # One stage, deliberately. A whole-file `docker build -f Dockerfile.lint .` # builds only the file's LAST stage, and sibling stages off a shared base # have no ordering edge between them, so a second stage sitting beside # this one would be silently skipped by exactly the invocation the # canonical org-wide `script/lint` uses -- a green that linted nothing, # which is the failure mode this file exists to prevent. With a single # stage there is nothing to skip and `script/lint` needs no `--target`. # If a second check is ever added here it must be chained (`FROM lint AS # ...`) or carry an explicit ordering edge, never left as a sibling. # # Only linting is containerised (owner ruling, 2026-08-10: "fmt and fmt # check arent docker, just linting"). script/fmt and script/fmt-check run # on the host, and the main Dockerfile runs the production build and the # format check directly -- see the comment there. # # The lint is invoked directly below rather than through `make lint` or # `script/lint`. That is not a style choice: `script/lint` IS this build, # so calling it from inside would recurse into a docker build with no # daemon. # # This repo's lint is a clean Hugo build that surfaces broken internal # links and template path problems: `hugo` fails on build errors and # --printPathWarnings reports render-target collisions. # alpine 3.21, 2026-02-28 FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 WORKDIR /src # Keep these four instructions byte-identical to the main Dockerfile's, # in the same order: Docker keys layers on the instruction chain, not on # the file they live in, so an identical prefix means this build is a # cache hit against the main image's layers. script/bootstrap compiles # the pinned Hugo from source, which is by far the most expensive step # here, and it must not be paid twice. The dependency layer is also # deliberately above the ARG below, so it stays cached and only the lint # step re-runs on every invocation. COPY script/ script/ RUN script/bootstrap COPY . . # CHECK_EPOCH is a per-invocation nonce supplied by script/lint. Without # it an unchanged tree serves the lint layer from cache: the lint never # executes and the build still exits 0, which is precisely the false # green this repo already fixed once in the main Dockerfile. Caching is # explicitly waived for lint, so the value is expanded into the linted # command as well as the guard -- two independent value-keyed # invalidation points, so a cache miss never depends on BuildKit's # treatment of an unreferenced ARG, and the epoch is visible in the build # log. Declared with no default: a default is a constant, and a constant # is a stable cache key. The guard makes a bare # `docker build -f Dockerfile.lint .` fail loudly instead of silently # reusing the empty (and therefore stable) cache key. Keep both # references. ARG CHECK_EPOCH RUN [ -n "$CHECK_EPOCH" ] || exit 1 RUN echo "lint epoch: ${CHECK_EPOCH}" && hugo --minify --printPathWarnings