Skip to content

fix(ci): pin hadolint image by digest to stop drift off the rev pin - #1697

Merged
cristim merged 1 commit into
mainfrom
chore/fix-hadolint-base-debt
Aug 3, 2026
Merged

cristim merged 1 commit into
mainfrom
chore/fix-hadolint-base-debt

Conversation

@cristim

@cristim cristim commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

  • pre-commit --all-files (the CI "Run pre-commit hooks" job lints the whole merge ref) started failing the hadolint-docker hook on main, blocking every open PR including ones that touch zero Docker files (e.g. sec(iac): scope Azure purchase role to onboarded subscription #1658)
  • Root cause is version drift, not new Dockerfile debt: the upstream hadolint-docker hook entry (ghcr.io/hadolint/hadolint hadolint) has no image tag, so it floats to :latest independent of this repo's rev: v2.14.0 pin, which only fixes the hook definition commit, not the Docker image. Upstream published hadolint 2.15.1 as latest, which applies DL3066 to any named USER directive and DL3025 to HEALTHCHECK's CMD sub-clause -- neither of which existed in 2.14.0's ruleset. None of the three Dockerfiles changed.
  • Replaces the external hadolint-docker hook with a local hook pinned to the immutable digest of the same v2.14.0 image, so the linter version can only move via an explicit bump here
  • Corrects a stale comment in .github/workflows/pre-commit.yml that had assumed the old hook was already image-pinned via the rev:
  • No Dockerfile changes

Verification

  • pre-commit run hadolint --all-files -> exit 0 (previously exit 1 on main)
  • pre-commit run --all-files -> only remaining failure is terraform_validate, and it's a local-machine artifact unrelated to this change (mkdir ... file exists race on the shared ~/.terraform.d/plugin-cache, reproduced consistently on retry, not present in this diff)
  • Confirmed via docker run ... hadolint --version: v2.14.0 tag = 2.14.0, latest tag = 2.15.1
  • Confirmed identical findings reproduce against latest and disappear against the pinned digest, on the unchanged Dockerfiles, with .hadolint.yaml's ignore list applied (matching what pre-commit actually mounts)
  • Built all three images locally to prove the config change doesn't affect the build: Dockerfile, Dockerfile.dev, Dockerfile.test all built successfully

Closes #1696

The hadolint-docker pre-commit hook's upstream entry
(ghcr.io/hadolint/hadolint hadolint) carries no image tag, so it floats
to :latest regardless of the hadolint/hadolint repo's rev: v2.14.0 pin
in .pre-commit-config.yaml, which only fixes the hook *definition*, not
the Docker image it runs. Upstream publishing hadolint 2.15.1 as latest
started failing pre-commit --all-files on every PR (new DL3066/DL3025
findings on unchanged Dockerfiles), since that job lints the whole
merge ref rather than the PR's diff.

Replace it with a local hook pinned to the immutable digest of the
same v2.14.0 image so the linter version can only change via an
explicit bump here. No Dockerfile changes were needed; verified
Dockerfile, Dockerfile.dev, and Dockerfile.test all still build.

Also corrects a stale comment in .github/workflows/pre-commit.yml that
had assumed the old hook was already image-pinned via the rev.

Closes #1696
@cristim cristim added triaged Item has been triaged priority/p0 Drop everything; same-day fix severity/high Significant harm urgency/now Drop other things impact/internal Team-internal only effort/s Hours type/chore Maintenance / non-user-visible labels Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 667b7b78-15a2-49b2-8797-74ed62cb1e79

📥 Commits

Reviewing files that changed from the base of the PR and between cd4ee03 and 1a9f8e2.

📒 Files selected for processing (2)
  • .github/workflows/pre-commit.yml
  • .pre-commit-config.yaml

Comment @coderabbitai help to get the list of available commands.

@cristim

cristim commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Root-cause record (merged)

Recording this because the diagnosis is more useful than the diff, and the obvious reading of it was wrong.

Symptom: pre-commit red on main since Jul 28, failing every open PR on hadolint-docker with four findings (Dockerfile DL3066 at :149 and DL3025 at :165, Dockerfile.dev:62, Dockerfile.test:21). None of the PRs it blocked touched a Dockerfile.

Ruled out first: the commits between the last green and the first red touch no Dockerfile*, no .hadolint.yaml, no .pre-commit-config.yaml. Dockerfile had not changed in weeks; .hadolint.yaml not since February. Inputs unchanged, behaviour changed.

The working hypothesis was "the pinned tag was rebuilt underneath us". That was wrong. The image was never pinned at all. Upstream's .pre-commit-hooks.yaml, at tag v2.14.0 itself, declares:

entry: ghcr.io/hadolint/hadolint hadolint

No image tag, so Docker resolves :latest on every run. rev: pins the hook definition, not the image it executes. Proven rather than inferred:

docker run ghcr.io/hadolint/hadolint:v2.14.0 hadolint --version  ->  2.14.0
docker run ghcr.io/hadolint/hadolint:latest  hadolint --version  ->  2.15.1

Registry digests confirm distinct images (v2.14.0 = sha256:27086352…, v2.15.1 = sha256:32dac941… = latest). Running both against the unchanged Dockerfiles with .hadolint.yaml's ignore list mounted: v2.14.0 exit 0, 2.15.1 exit 1 with exactly the four CI findings.

Fix: replaced the external hook with a repo: local hook pinned to the immutable digest of v2.14.0. No .hadolint.yaml ignored: entries, nothing masked. Population check: hadolint-docker is the only Docker-language hook in the config — every other pinned repo builds from source at its rev, so no sweep was needed.

No Dockerfile changes were made, deliberately. All four findings are new-rule behaviour in 2.15.x, and one of them could not be "fixed" safely: DL3025 fired on HEALTHCHECK's embedded CMD, not the container's actual ENTRYPOINT/CMD (both already exec form). Converting curl -f … || exit 1 to JSON form is impossible without breaking the fallback, since exec form has no shell operators — so editing the Dockerfile to satisfy the linter would have introduced a real bug for no gain.

Also corrected: a comment in .github/workflows/pre-commit.yml asserted the hook already ran the official v2.14.0 image. That assumption was wrong from the start and is likely why the float went unnoticed.

The expensive part, for the record

The four lint findings were not the real cost. A permanently-red required check that is nobody's fault trains reviewers to stop reading that signal, which is how a genuine failure gets waved through later. That is why this was treated as urgent rather than tolerated.

Follow-up #1701 / PR #1700 adopts hadolint 2.15.1 properly and fixes the four findings, so the pin does not mean never picking up new rules — the slow version of the same drift.

cristim added a commit that referenced this pull request Sep 27, 2026
…1697)

The hadolint-docker pre-commit hook's upstream entry
(ghcr.io/hadolint/hadolint hadolint) carries no image tag, so it floats
to :latest regardless of the hadolint/hadolint repo's rev: v2.14.0 pin
in .pre-commit-config.yaml, which only fixes the hook *definition*, not
the Docker image it runs. Upstream publishing hadolint 2.15.1 as latest
started failing pre-commit --all-files on every PR (new DL3066/DL3025
findings on unchanged Dockerfiles), since that job lints the whole
merge ref rather than the PR's diff.

Replace it with a local hook pinned to the immutable digest of the
same v2.14.0 image so the linter version can only change via an
explicit bump here. No Dockerfile changes were needed; verified
Dockerfile, Dockerfile.dev, and Dockerfile.test all still build.

Also corrects a stale comment in .github/workflows/pre-commit.yml that
had assumed the old hook was already image-pinned via the rev.

Closes #1696
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/internal Team-internal only priority/p0 Drop everything; same-day fix severity/high Significant harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/now Drop other things

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(ci): hadolint pre-commit hook floats to :latest, decoupled from rev: pin

1 participant