# Lint-only image. `script/lint` builds this file and nothing else: the # linter runs as a build step, so a successful build IS a clean lint. # Building rather than bind-mounting is what makes it work where the # docker daemon is remote and bind mounts are impossible. # # The linter is invoked directly below rather than through `make 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 linter is prettier over markdown. A Go repo's version of # this file differs only in the base image and the two lint commands; # see the containerised-lint rule in prompts/REPO_POLICIES.md. # # node 22-alpine, 2026-02-22 FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 WORKDIR /app # Dependency layer first, and deliberately above the ARG below, so it # stays cached and only the lint steps re-run on every invocation. # Without that ordering the cache-bust would reinstall dependencies on # every lint and make linting network-dependent. COPY script/ script/ COPY package.json yarn.lock ./ 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 and the build # reports a lint it never ran — a green that proves nothing, which is # the whole failure mode this file exists to avoid reintroducing. The # guard makes a bare `docker build -f Dockerfile.lint .` fail loudly # instead of silently reusing the empty (and therefore stable) cache # key. The value is expanded into the lint command as well, so the cache # miss does not depend on BuildKit's handling of an unreferenced ARG and # the epoch is visible in the build log. Keep both references. ARG CHECK_EPOCH RUN [ -n "$CHECK_EPOCH" ] || exit 1 RUN echo "lint epoch: ${CHECK_EPOCH}" && \ yarn run prettier --check '**/*.md' --tab-width 4 --prose-wrap always