ci: bypass buildx --push auth via per-platform --load + docker push + manifest

buildx build --push has failed 401 on every attempt. The buildkit auth
forwarding (whether via docker-container or docker driver) does not work
reliably on this builder.

New strategy: build each platform separately with --load (into local
docker, which can read /root/.docker/config.json), then push with
native docker push, then assemble a multi-arch manifest with
docker manifest create/push.

Per-platform tags are pushed as :<BUILD_ID>-amd64 / :<BUILD_ID>-arm64
and the manifest combines them under the canonical :<BUILD_ID> and :latest.

This replaces a single buildx --push call with:
  1. buildx build --platform linux/amd64 --load
  2. docker push (amd64)
  3. docker rmi  (free disk)
  4. buildx build --platform linux/arm64 --load
  5. docker push (arm64)
  6. docker rmi  (free disk)
  7. docker manifest create + push (multi-arch)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 20:24:27 +08:00
co-authored by Claude Opus 4.8
parent 75b07bf10e
commit 761041a70e
Vendored
+84 -29
View File
@@ -90,17 +90,6 @@ pipeline {
# Verify emulation works before proceeding # Verify emulation works before proceeding
sudo docker run --rm --platform linux/arm64 docker.m.daocloud.io/library/alpine:latest uname -m sudo docker run --rm --platform linux/arm64 docker.m.daocloud.io/library/alpine:latest uname -m
# Use the default docker driver so registry auth is shared with the
# host daemon (the docker-container driver requires manual session
# forwarding which has been unreliable on this builder).
if ! sudo docker buildx inspect --builder ${BUILDER_NAME} >/dev/null 2>&1; then
sudo docker buildx create \
--name ${BUILDER_NAME} \
--driver docker \
--use
fi
sudo docker buildx inspect --builder ${BUILDER_NAME} --bootstrap >/dev/null
""" """
} }
} }
@@ -109,6 +98,7 @@ pipeline {
steps { steps {
sh """ sh """
set -e set -e
run_with_heartbeat() { run_with_heartbeat() {
log_file="\$1" log_file="\$1"
build_label="\$2" build_label="\$2"
@@ -144,17 +134,26 @@ pipeline {
fi fi
return "\$cmd_status" return "\$cmd_status"
} }
sudo docker buildx inspect --builder ${BUILDER_NAME} --bootstrap >/dev/null
echo 'Building and pushing multi-arch API image: ${API_IMAGE_TAGGED}' # ── Build each platform separately, push with docker CLI,
run_with_heartbeat /tmp/live-recorder-api-buildx-${IMAGE_TAG}.log "API multi-arch build" \ # then assemble a multi-arch manifest.
# buildx --push does not forward auth reliably on this
# builder; per-platform --load + docker push + manifest
# bypasses that entirely.
for PLATFORM in linux/amd64 linux/arm64; do
ARCH_TAG="\${PLATFORM##*/}" # amd64 / arm64
PLAT_REF="${API_IMAGE_TAGGED}-\${ARCH_TAG}"
PLAT_LATEST="${API_IMAGE_LATEST}-\${ARCH_TAG}"
echo ''
echo "=== Building API for \$PLATFORM -> \$PLAT_REF ==="
run_with_heartbeat /tmp/live-recorder-api-\${ARCH_TAG}.log "API \${ARCH_TAG} build" \
sudo docker buildx build \ sudo docker buildx build \
--builder ${BUILDER_NAME} \ --platform "\$PLATFORM" \
--platform ${TARGET_PLATFORMS} \
--network host \ --network host \
--progress=plain \ --progress=plain \
--provenance=false \ --provenance=false \
--cache-from type=registry,ref=${API_CACHE_IMAGE} \ --cache-from type=registry,ref=${API_CACHE_IMAGE} \
--cache-to type=registry,ref=${API_CACHE_IMAGE},mode=max \
--build-arg HTTP_PROXY=${HTTP_PROXY_URL} \ --build-arg HTTP_PROXY=${HTTP_PROXY_URL} \
--build-arg HTTPS_PROXY=${HTTP_PROXY_URL} \ --build-arg HTTPS_PROXY=${HTTP_PROXY_URL} \
--build-arg NO_PROXY=${NO_PROXY_HOSTS} \ --build-arg NO_PROXY=${NO_PROXY_HOSTS} \
@@ -162,10 +161,37 @@ pipeline {
--build-arg https_proxy=${HTTP_PROXY_URL} \ --build-arg https_proxy=${HTTP_PROXY_URL} \
--build-arg no_proxy=${NO_PROXY_HOSTS} \ --build-arg no_proxy=${NO_PROXY_HOSTS} \
-f src/LiveRecorder.WebApi/Dockerfile \ -f src/LiveRecorder.WebApi/Dockerfile \
-t ${API_IMAGE_TAGGED} \ -t "\$PLAT_REF" \
-t ${API_IMAGE_LATEST} \ -t "\$PLAT_LATEST" \
--push \ --load \
. .
echo "=== Pushing \$PLAT_REF ==="
sudo docker push "\$PLAT_REF" 2>&1 | tail -5
sudo docker push "\$PLAT_LATEST" 2>&1 | tail -5
# Free disk space before the next platform build
sudo docker rmi "\$PLAT_REF" "\$PLAT_LATEST" 2>/dev/null || true
done
echo ''
echo "=== Creating multi-arch manifest: ${API_IMAGE_TAGGED} ==="
sudo docker manifest create "${API_IMAGE_TAGGED}" \
"${API_IMAGE_TAGGED}-amd64" \
"${API_IMAGE_TAGGED}-arm64"
sudo docker manifest create "${API_IMAGE_LATEST}" \
"${API_IMAGE_LATEST}-amd64" \
"${API_IMAGE_LATEST}-arm64"
echo "=== Pushing manifests ==="
sudo docker manifest push "${API_IMAGE_TAGGED}" 2>&1 | tail -3
sudo docker manifest push "${API_IMAGE_LATEST}" 2>&1 | tail -3
# (Optionally clean up per-platform tags from registry, but harmless to leave them)
echo ''
echo "API multi-arch images pushed successfully:"
echo " ${API_IMAGE_TAGGED}"
echo " ${API_IMAGE_LATEST}"
""" """
} }
} }
@@ -174,6 +200,7 @@ pipeline {
steps { steps {
sh """ sh """
set -e set -e
run_with_heartbeat() { run_with_heartbeat() {
log_file="\$1" log_file="\$1"
build_label="\$2" build_label="\$2"
@@ -209,17 +236,21 @@ pipeline {
fi fi
return "\$cmd_status" return "\$cmd_status"
} }
sudo docker buildx inspect --builder ${BUILDER_NAME} --bootstrap >/dev/null
echo 'Building and pushing multi-arch Web image: ${WEB_IMAGE_TAGGED}' for PLATFORM in linux/amd64 linux/arm64; do
run_with_heartbeat /tmp/live-recorder-web-buildx-${IMAGE_TAG}.log "Web multi-arch build" \ ARCH_TAG="\${PLATFORM##*/}"
PLAT_REF="${WEB_IMAGE_TAGGED}-\${ARCH_TAG}"
PLAT_LATEST="${WEB_IMAGE_LATEST}-\${ARCH_TAG}"
echo ''
echo "=== Building Web for \$PLATFORM -> \$PLAT_REF ==="
run_with_heartbeat /tmp/live-recorder-web-\${ARCH_TAG}.log "Web \${ARCH_TAG} build" \
sudo docker buildx build \ sudo docker buildx build \
--builder ${BUILDER_NAME} \ --platform "\$PLATFORM" \
--platform ${TARGET_PLATFORMS} \
--network host \ --network host \
--progress=plain \ --progress=plain \
--provenance=false \ --provenance=false \
--cache-from type=registry,ref=${WEB_CACHE_IMAGE} \ --cache-from type=registry,ref=${WEB_CACHE_IMAGE} \
--cache-to type=registry,ref=${WEB_CACHE_IMAGE},mode=max \
--build-arg NODE_IMAGE=${WEB_NODE_IMAGE} \ --build-arg NODE_IMAGE=${WEB_NODE_IMAGE} \
--build-arg NGINX_IMAGE=${WEB_NGINX_IMAGE} \ --build-arg NGINX_IMAGE=${WEB_NGINX_IMAGE} \
--build-arg HTTP_PROXY=${HTTP_PROXY_URL} \ --build-arg HTTP_PROXY=${HTTP_PROXY_URL} \
@@ -230,10 +261,34 @@ pipeline {
--build-arg no_proxy=${NO_PROXY_HOSTS} \ --build-arg no_proxy=${NO_PROXY_HOSTS} \
-f frontend/Dockerfile \ -f frontend/Dockerfile \
--build-arg VITE_API_BASE_URL=/api \ --build-arg VITE_API_BASE_URL=/api \
-t ${WEB_IMAGE_TAGGED} \ -t "\$PLAT_REF" \
-t ${WEB_IMAGE_LATEST} \ -t "\$PLAT_LATEST" \
--push \ --load \
frontend frontend
echo "=== Pushing \$PLAT_REF ==="
sudo docker push "\$PLAT_REF" 2>&1 | tail -3
sudo docker push "\$PLAT_LATEST" 2>&1 | tail -3
sudo docker rmi "\$PLAT_REF" "\$PLAT_LATEST" 2>/dev/null || true
done
echo ''
echo "=== Creating multi-arch manifest: ${WEB_IMAGE_TAGGED} ==="
sudo docker manifest create "${WEB_IMAGE_TAGGED}" \
"${WEB_IMAGE_TAGGED}-amd64" \
"${WEB_IMAGE_TAGGED}-arm64"
sudo docker manifest create "${WEB_IMAGE_LATEST}" \
"${WEB_IMAGE_LATEST}-amd64" \
"${WEB_IMAGE_LATEST}-arm64"
echo "=== Pushing manifests ==="
sudo docker manifest push "${WEB_IMAGE_TAGGED}" 2>&1 | tail -3
sudo docker manifest push "${WEB_IMAGE_LATEST}" 2>&1 | tail -3
echo ''
echo "Web multi-arch images pushed successfully:"
echo " ${WEB_IMAGE_TAGGED}"
echo " ${WEB_IMAGE_LATEST}"
""" """
} }
} }