docker-publish: use BuildKit (buildx) instead of legacy builder

The runners share one host Docker daemon across replicas; the legacy
builder corrupts layers under that concurrency. buildx builds in an
isolated buildkitd container. buildx is installed as a static binary,
keeping the no-marketplace-actions property.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andras Schmelczer 2026-06-06 14:50:20 +01:00
parent 8b0ef6d709
commit f2a58b2fb3

View file

@ -1,9 +1,15 @@
name: Build & publish Docker image
description: >-
Build a Docker image and push it to this Forgejo instance's container
registry. Reimplements docker/login-action + docker/metadata-action +
docker/build-push-action in plain shell using the docker CLI — no
marketplace actions are fetched or run.
Build a Docker image with BuildKit (buildx) and push it to this Forgejo
instance's container registry. Reimplements docker/login-action +
docker/metadata-action + docker/setup-buildx-action + docker/build-push-action
in plain shell using the docker CLI and a statically-installed buildx plugin —
no marketplace actions are fetched or run.
BuildKit (not the legacy builder) is required: the runners share one host
Docker daemon across replicas, and the legacy builder corrupts layers under
that concurrency ("failed to get layer ... layer does not exist"). buildx
builds in an isolated buildkitd container, which is robust against it.
inputs:
context:
@ -61,6 +67,30 @@ runs:
fi
docker --version
# --- Ensure the buildx plugin exists (static binary, not a marketplace action) ---
if ! docker buildx version >/dev/null 2>&1; then
case "$(uname -m)" in
x86_64) BX_ARCH=amd64 ;;
aarch64|arm64) BX_ARCH=arm64 ;;
*) BX_ARCH="$(uname -m)" ;;
esac
mkdir -p /usr/local/lib/docker/cli-plugins
curl -fsSL --retry 3 --retry-connrefused \
"https://github.com/docker/buildx/releases/download/v0.19.3/buildx-v0.19.3.linux-${BX_ARCH}" \
-o /usr/local/lib/docker/cli-plugins/docker-buildx
chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
fi
docker buildx version
# A host-networked buildkit builder so it can reach the registry on 127.0.0.1
# (buildkit treats loopback registries as insecure/HTTP automatically). A fixed
# name lets concurrent jobs share one builder and its cache; the inspect||create
# guard tolerates the create race between simultaneous jobs.
docker buildx inspect ci >/dev/null 2>&1 \
|| docker buildx create --name ci --driver docker-container --driver-opt network=host >/dev/null 2>&1 \
|| true
docker buildx use ci
# --- Resolve the registry host (canonical form of the block copy-pasted across repos) ---
host="${INPUT_REGISTRY_HOST:-${CONTAINER_REGISTRY_HOST:-$SERVER_URL}}"
host="${host#https://}"; host="${host#http://}"; host="${host%/}"
@ -91,16 +121,19 @@ runs:
[ -n "$a" ] && extra+=( --build-arg "$a" )
done <<< "${INPUT_BUILD_ARGS}"
# --- Build ---
docker build "${extra[@]}" \
# --- Log in before building so buildx can push directly (skipped on PRs) ---
if [ "${INPUT_PUSH}" = "true" ]; then
printf '%s' "${INPUT_TOKEN}" | docker login "$host" -u "$OWNER" --password-stdin
output=( --push )
else
# Pull-request builds validate the Dockerfile without producing/pushing an image.
output=()
fi
# --- Build (and push when requested) with BuildKit ---
docker buildx build "${extra[@]}" "${output[@]}" \
--label "org.opencontainers.image.source=${SERVER_URL}/${REPO}" \
--label "org.opencontainers.image.revision=${SHA}" \
--label "org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
"${tags[@]}" \
"${INPUT_CONTEXT}"
# --- Push (skipped on pull requests by passing push: false) ---
if [ "${INPUT_PUSH}" = "true" ]; then
printf '%s' "${INPUT_TOKEN}" | docker login "$host" -u "$OWNER" --password-stdin
docker push --all-tags "$image"
fi