Lint by piping files to the linters instead of bind-mounting
Build Images / ${{ fromJSON(needs.select.outputs.images) }} (push) Successful in 3m33s
Build Images / Select images (push) Successful in 41s
Lint / lint (push) Successful in 44s

`make lint` passed locally but failed in Gitea Actions with every Dockerfile
reported as missing:

    hadolint: images/node-agent/Dockerfile: withBinaryFile: does not exist

The job itself runs in a container, and the checkout lives in a docker volume
rather than on the host filesystem. Starting hadolint as a sibling container
through the host's docker socket meant `-v "$(CURDIR)":/repo` was resolved
against the HOST, where /workspace/... does not exist — so Docker created an
empty directory and mounted that.

Stream each file in on stdin instead. No shared filesystem is involved, so the
same command works locally and in CI. The linters then only ever see "-" as the
filename, so hack/lint.sh prints the real path itself, and only on failure.

Reproduced the original error and verified the fix against a faithful local
simulation: the repo in a docker volume at a path absent from the host, with
the socket mounted, running the real runner image.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
2026-08-09 11:48:50 -04:00
co-authored by Claude Opus 5
parent 1878df96ac
commit 7a81afec65
2 changed files with 72 additions and 9 deletions
+2 -9
View File
@@ -14,10 +14,6 @@ TARGETS := $(if $(IMAGE),$(IMAGE),$(IMAGES))
# CI overrides REF with the real published ref so the smoke test runs against it. # CI overrides REF with the real published ref so the smoke test runs against it.
REF ?= REF ?=
HADOLINT ?= hadolint/hadolint:latest-alpine
SHELLCHECK ?= koalaman/shellcheck:stable
SCRIPTS := $(wildcard hack/*.sh images/*/test.sh template/test.sh)
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
.PHONY: help list check lint hadolint shellcheck build test all .PHONY: help list check lint hadolint shellcheck build test all
@@ -40,13 +36,10 @@ check: ## Verify each image directory follows the expected layout
exit $$rc exit $$rc
hadolint: ## Lint every Dockerfile hadolint: ## Lint every Dockerfile
@docker run --rm -v "$(CURDIR)":/repo:ro -w /repo $(HADOLINT) \ @hack/lint.sh dockerfiles
hadolint $(addprefix images/,$(addsuffix /Dockerfile,$(IMAGES))) template/Dockerfile
@echo "hadolint ok"
shellcheck: ## Lint every shell script shellcheck: ## Lint every shell script
@docker run --rm -v "$(CURDIR)":/repo:ro -w /repo $(SHELLCHECK) $(SCRIPTS) @hack/lint.sh scripts
@echo "shellcheck ok"
lint: hadolint shellcheck ## Run all linters lint: hadolint shellcheck ## Run all linters
Executable
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Lint Dockerfiles with hadolint and shell scripts with shellcheck.
#
# Each file is streamed into the linter container on stdin instead of bind-mounting
# the repository. That is deliberate: in Gitea Actions the job itself runs in a
# container, and the checkout lives in a docker volume rather than on the host
# filesystem. A sibling container started through the host's docker socket resolves
# `-v "$PWD":/repo` against the HOST, where that path does not exist — Docker then
# silently creates an empty directory and the linter reports every file as missing.
# Piping needs no shared filesystem, so one command works locally and in CI.
#
# The linters therefore only ever see "-" as the filename, so the real path is
# printed here. Note this also means shellcheck cannot follow `source` directives;
# none of these scripts use them.
#
# Usage: lint.sh [dockerfiles|scripts|all]
set -euo pipefail
cd "$(dirname "$0")/.."
HADOLINT_IMAGE=${HADOLINT_IMAGE:-hadolint/hadolint:latest-alpine}
SHELLCHECK_IMAGE=${SHELLCHECK_IMAGE:-koalaman/shellcheck:stable}
rc=0
report() {
local file=$1 out=$2 ok=$3
if [ "$ok" = 0 ]; then
printf ' ok %s\n' "$file"
else
printf ' FAIL %s\n' "$file"
[ -n "$out" ] && printf '%s\n' "$out" | sed 's/^/ /'
rc=1
fi
}
dockerfiles() {
local file out ok
for file in images/*/Dockerfile template/Dockerfile; do
[ -f "$file" ] || continue
ok=0
out=$(docker run --rm --interactive "$HADOLINT_IMAGE" \
hadolint --no-color - <"$file" 2>&1) || ok=$?
report "$file" "$out" "$ok"
done
}
scripts() {
local file out ok
for file in hack/*.sh images/*/test.sh template/test.sh; do
[ -f "$file" ] || continue
ok=0
out=$(docker run --rm --interactive "$SHELLCHECK_IMAGE" - <"$file" 2>&1) || ok=$?
report "$file" "$out" "$ok"
done
}
case "${1:-all}" in
dockerfiles) dockerfiles ;;
scripts) scripts ;;
all)
dockerfiles
scripts
;;
*)
echo "usage: ${0##*/} [dockerfiles|scripts|all]" >&2
exit 2
;;
esac
exit "$rc"