Skip to content

feat: a docker stack that lints Dockerfiles with hadolint - #66

Merged
vannt-dev merged 2 commits into
mainfrom
feat/docker-stack
Oct 13, 2026
Merged

vannt-dev merged 2 commits into
mainfrom
feat/docker-stack

Conversation

@vannt-dev

@vannt-dev vannt-dev commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

What

A twelfth stack, docker: a repository's Dockerfiles are linted in CI and their base images kept up to date.

# .github/workflows/ci.yml of a Node.js project with a Dockerfile, after `repokeeper init`
jobs:
  docker:
    uses: vannt-dev/repokeeper/.github/workflows/stack-docker.yml@v0
    with:
      dockerfiles: '["Dockerfile","Dockerfile.dev"]'
      build: "false"
  node:
    uses: vannt-dev/repokeeper/.github/workflows/stack-node.yml@v0
  • Detection: Dockerfile or Containerfile, also with a suffix (Dockerfile.dev) or named <name>.Dockerfile. Not Dockerfile.dockerignore, and not a compose file alone.
  • Lint: hadolint 2.15.1 on every Dockerfile at the stack's root. Errors and warnings fail the job; information-level rules are printed. A .hadolint.yaml beside the files is used as it is. This is the line the script stack draws for PSScriptAnalyzer.
  • Dependency updates: Dependabot's docker ecosystem, and docker-compose when there is a compose file.
  • Options: stack_options.docker.files names the Dockerfiles; build: true also runs docker build on each, with the stack's folder as context, pushing nothing.
  • GitLab: the lint, in hadolint/hadolint:v2.15.1-debian, which Renovate is told to leave alone like the other managed images. build is not written there and check says so.

Decisions to check

  1. The docker stack is kept beside another stack. The script stack is dropped beside a project ("scripts beside another stack belong to that stack"). A Dockerfile is different: no other stack's job reads it, and most Dockerfiles live beside an application. So init in a Node.js project with a Dockerfile now writes stacks: [node, docker]. Existing repositories are not touched: update reads the stacks from .repokeeper.yml.
  2. The build is off by default. A build can need a context, build arguments or secrets that a lint does not, and a stack that fails on its first run for that reason helps nobody.
  3. Warnings fail, information does not. hadolint's own default fails on every rule, including information-level ones such as DL3066.
  4. A Dockerfile at the root does not make the root the project. A monorepo with web/, api/ and a root Dockerfile is still looked into; the result is stacks: [node, go, docker] with docker at the root. Before this change the root held no stack there, so the folders were found; without this rule the new stack would have hidden them.
  5. A repository that is only an image keeps its scripts as a stack ([script, docker]): there is no project for the scripts to belong to.

The standard version does not move: nothing changes for a repository that has no docker in its stacks.

Tested

  • Unit tests written first and seen failing: detection, what the stack resolves to, the options, the layout rules above, the GitLab job and its quoting of file names (test/stacks-docker.test.ts, 10), the reusable workflow and its two jobs in workflow-tests (test/workflows.test.ts), the GitLab pipeline from the fixture (test/platforms-gitlab-stacks.test.ts). 454 tests in all, typecheck, lint, build, npm run standard.
  • hadolint itself, the 2.15.1 release binary (checksum equal to the release's checksums.sha256): the fixture is clean; a file with an information-level finding only exits 0 with --failure-threshold warning and 1 without it; a file with warnings exits 1; a .hadolint.yaml that fails on errors only is obeyed. The first fixture I wrote failed on DL3066, which is how decision 3 came about.
  • The package itself: npm pack, the tarball installed in an empty project and init run in a new repository with package.json, Dockerfile, Dockerfile.dev, compose.yaml and a shell script: stacks: [node, docker], the ci.yml above, and Dependabot entries for npm, docker, docker-compose and github-actions.
  • Real runs: workflow-tests calls stack-docker.yml on fixtures/docker twice, once as the fixture resolves (lint) and once with build: "true". See the docker and docker-build checks of this pull request.

Not tested

  • The GitLab job on a real GitLab runner. The pipeline is generated from the fixture and compared; the image tag exists on Docker Hub; the script is hadolint with the same flags that were run locally.
  • docker build locally: there is no Docker on this machine. The docker-build check of this pull request is that run.

With #65

Independent of #64 and #65, but not without overlap with #65. Whichever of the two is merged second has to be brought up to date with main, for two reasons:

  • Both add lines at the same places of .github/workflows/workflow-tests.yml (the list of paths and the end of the file) and of test/workflows.test.ts (the REUSABLE list and the tests before "cancels superseded"), so git will report conflicts there. Both sides are kept.
  • #65 gives every stack a line in CODEQL_LANGUAGES, so the docker stack needs docker: null there (CodeQL has no Dockerfile language). typecheck fails without it, so it cannot be missed.

A repository's Dockerfile was no stack's business: nothing checked it and
nothing kept its base image up to date.

The docker stack is detected from a Dockerfile or Containerfile, also with a
suffix or named <name>.Dockerfile. Unlike scripts it is kept beside another
stack, since no other stack's job reads a Dockerfile. Its CI job calls the new
reusable workflow stack-docker.yml, which runs hadolint 2.15.1 on every
Dockerfile at the stack's root. Errors and warnings fail the job and
information-level rules are printed; a .hadolint.yaml beside the files is
used as it is. Dependabot watches the base images, and those of a compose
file when there is one.

stack_options.docker takes `files`, to name the Dockerfiles, and `build`, to
build each image as well without pushing it. The build is off unless asked
for: it may need a context, arguments or secrets a lint does not.

On GitLab the lint runs in the hadolint image, which Renovate is told to
leave alone like the other managed images; `build` is not written there, and
repokeeper says so.

Detection: a Dockerfile at the root does not make the root the project, so a
monorepo with one is still looked into, and the Dockerfile stays a stack at
the root. A repository that is only an image keeps its scripts as a stack.
# Conflicts:
#	.github/workflows/workflow-tests.yml
#	test/workflows.test.ts
@vannt-dev
vannt-dev merged commit de665b1 into main Oct 13, 2026
28 checks passed
@vannt-dev
vannt-dev deleted the feat/docker-stack branch October 13, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant