This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch feat/ci-publish-dockerhub-on-tag
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit 80281674a46fc86a4d3846c62cbac6896d552328
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 24 09:18:17 2026 +0800

    ci: mirror multi-arch image to Docker Hub on tag-push; retire script Step 5
    
    CI already builds the per-arch image natively for every commit + tag and
    publishes the multi-arch manifest to GHCR. Add a final step in the
    manifest job (gated on `v*` tag pushes) that mirrors the same multi-arch
    manifest to Docker Hub via `docker buildx imagetools create`, exactly
    the way skywalking-graalvm-distro does it. Same blobs, registry-level
    manifest copy — no rebuild, no QEMU.
    
    Tagging on the shared `apache/skywalking-ui` repo follows Horizon's
    convention: `:horizon-<v>` (immutable) + `:latest` (newest Horizon
    release). Booster-ui uses its own tags on the same repo.
    
    release-finalize.sh Step 5 is no longer the source of truth — it now
    verifies the Docker Hub tags landed (which they should have, since CI
    runs on tag-push). If CI didn't publish (workflow failed / DOCKERHUB
    secrets missing / tag pushed before this workflow shipped), the script
    falls back to the manual `imagetools create` mirror — same command CI
    runs, just from a workstation with Docker Hub push rights. That covers
    the case we hit finalizing 0.5.0 (CI workflow predates this change, so
    the v0.5.0 tag didn't get the Docker Hub mirror).
    
    Also adds scripts/.finalize-work to .gitignore — same pattern as
    scripts/.release-work; release-script working trees aren't artifacts
    we want to track.
    
    Secrets required on the repo for the new CI step (set by an org admin):
      DOCKERHUB_USER   — Docker Hub account with push rights on 
apache/skywalking-ui
      DOCKERHUB_TOKEN  — PAT for that account
---
 .github/workflows/publish-image.yaml |  44 ++++++++++++++-
 .gitignore                           |   3 +-
 scripts/release-finalize.sh          | 104 ++++++++++++++++++-----------------
 3 files changed, 98 insertions(+), 53 deletions(-)

diff --git a/.github/workflows/publish-image.yaml 
b/.github/workflows/publish-image.yaml
index baaeae2..047339d 100644
--- a/.github/workflows/publish-image.yaml
+++ b/.github/workflows/publish-image.yaml
@@ -14,9 +14,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-# Publish a multi-arch Horizon UI container image to GHCR on:
-#   - push to `main`  (tagged with `main` + the full commit SHA)
-#   - any `v*` tag    (tagged with the version + the full commit SHA)
+# Publish a multi-arch Horizon UI container image:
+#   - GHCR on every push to `main` and every `v*` tag.
+#   - Docker Hub (`apache/skywalking-ui`) on `v*` tags only — release
+#     mirror, not per-commit. The Docker Hub repo is shared with
+#     booster-ui, so Horizon's tag prefix is `horizon-<v>` and the
+#     `latest` tag on this repo points at the newest Horizon release.
 #
 # Architecture story
 #   The Dockerfile builds `dist/` from source inside the image. The
@@ -198,3 +201,38 @@ jobs:
             echo "::endgroup::"
           done
           echo "::notice::Published multi-arch manifest @sha=${{ github.sha }} 
(amd64+arm64)"
+
+      # ── Mirror to Docker Hub on `v*` tags only.
+      #
+      # `apache/skywalking-ui` is shared with booster-ui; Horizon's
+      # release tags are `horizon-<v>` and the `latest` tag (Horizon
+      # owns `latest` on this repo as the newest Horizon release —
+      # booster-ui uses its own dated tags).
+      #
+      # `docker buildx imagetools create` does a manifest-level copy of
+      # the GHCR canonical sha tag (already multi-arch from the loop
+      # above) into the two Docker Hub tags. No rebuild, copies blobs
+      # across registries via the registry-to-registry mount API.
+      - name: Log in to Docker Hub (release only)
+        if: github.ref_type == 'tag'
+        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
+        with:
+          username: ${{ secrets.DOCKERHUB_USER }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Mirror multi-arch manifest to Docker Hub (release only)
+        if: github.ref_type == 'tag'
+        env:
+          DOCKERHUB_IMAGE: apache/skywalking-ui
+        run: |
+          set -eu
+          ver="${GITHUB_REF_NAME#v}"
+          src="${BASE}:${{ github.sha }}"
+          echo "::group::Mirror ${src} → ${DOCKERHUB_IMAGE}:horizon-${ver} + 
:latest"
+          docker buildx imagetools create \
+            -t "${DOCKERHUB_IMAGE}:horizon-${ver}" \
+            -t "${DOCKERHUB_IMAGE}:latest" \
+            "${src}"
+          docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:horizon-${ver}" 
| head -40
+          echo "::endgroup::"
+          echo "::notice::Mirrored to 
${DOCKERHUB_IMAGE}:{horizon-${ver},latest}"
diff --git a/.gitignore b/.gitignore
index 694f426..d068795 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,4 +43,5 @@ horizon-wire.jsonl
 horizon-setup.json
 
 # Release
-scripts/.release-work
\ No newline at end of file
+scripts/.release-work
+scripts/.finalize-work
\ No newline at end of file
diff --git a/scripts/release-finalize.sh b/scripts/release-finalize.sh
index 671c738..c160c00 100755
--- a/scripts/release-finalize.sh
+++ b/scripts/release-finalize.sh
@@ -36,11 +36,13 @@
 #      (src + bin tarballs + .asc + .sha512) fetched back from SVN release,
 #      with the CHANGELOG section for <v> as the body.
 #
-#   3. Build + push the multi-arch (amd64 + arm64) container image to
-#      Docker Hub apache/skywalking-ui, tagged:
-#         :horizon-<v>     immutable, this release
-#         :latest          moving — newest Horizon release. On this shared
-#                          repo, `latest` serves Horizon (not booster-ui).
+#   3. Verify the Docker Hub multi-arch image — CI's publish-image
+#      workflow mirrors the GHCR image to Docker Hub automatically on
+#      every `v*` tag push (apache/skywalking-ui:horizon-<v> and
+#      apache/skywalking-ui:latest). This step just confirms the two
+#      expected tags are present. Falls back to a manual local mirror
+#      (same `docker buildx imagetools create` CI runs) if CI didn't
+#      publish — needs Docker Hub push rights on apache/skywalking-ui.
 #
 # Usage:  bash scripts/release-finalize.sh
 #
@@ -59,8 +61,6 @@ 
SVN_RELEASE_URL="https://dist.apache.org/repos/dist/release/skywalking/horizon-u
 
 DOCKERHUB_REPO="apache/skywalking-ui"
 WORK_DIR="${SCRIPT_DIR}/.finalize-work"
-BUILDER_NAME="horizon-release-builder"
-
 # ========================== Helpers ==========================
 
 err() { echo "ERROR: $*" >&2; }
@@ -89,7 +89,8 @@ if [ ${#MISSING[@]} -gt 0 ]; then
 fi
 
 if ! docker buildx version >/dev/null 2>&1; then
-    err "docker buildx is required for the multi-arch image build."
+    err "docker buildx is required (Step 5 uses 'imagetools create' to copy"
+    err "the CI-built multi-arch manifest from GHCR to Docker Hub)."
     exit 1
 fi
 
@@ -248,7 +249,7 @@ else
     if confirm "Create the GitHub release ${TAG} and attach the 6 artifacts?"; 
then
         gh release create "${TAG}" \
             --repo apache/skywalking-horizon-ui \
-            --title "Apache SkyWalking Horizon UI ${RELEASE_VERSION}" \
+            --title "${RELEASE_VERSION}" \
             --notes-file "${NOTES_FILE}" \
             "${ART_DIR}/${SRC_BASE}" \
             "${ART_DIR}/${SRC_BASE}.asc" \
@@ -265,47 +266,52 @@ fi
 # ========================== Step 5: Docker Hub multi-arch image 
==========================
 note "Step 5 — Docker Hub image: ${DOCKERHUB_REPO}"
 
-# Build from a CLEAN checkout of the tag so the image matches the released
-# source exactly (no local uncommitted edits leak in).
-BUILD_SRC="${WORK_DIR}/src"
-echo "Checking out ${TAG} into ${BUILD_SRC}…"
-git -C "${PROJECT_DIR}" archive --format=tar --prefix=src/ "${TAG}" | (cd 
"${WORK_DIR}" && tar -x)
-
-# A docker-container builder is required: the default 'docker' driver cannot
-# emit a multi-platform manifest. Create one if absent + ensure QEMU is set
-# up for the foreign-arch emulation.
-if ! docker buildx inspect "${BUILDER_NAME}" >/dev/null 2>&1; then
-    echo "Creating buildx builder '${BUILDER_NAME}' (docker-container driver)…"
-    docker buildx create --name "${BUILDER_NAME}" --driver docker-container 
--bootstrap
-fi
-docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 
>/dev/null 2>&1 || true
-
-# Horizon publishes the immutable per-release tag plus the moving `latest`
-# on the shared repo. `latest` here points at the newest Horizon release —
-# pulling `${DOCKERHUB_REPO}:latest` gets Horizon, not booster-ui.
-IMG_TAGS=(-t "${DOCKERHUB_REPO}:horizon-${RELEASE_VERSION}" -t 
"${DOCKERHUB_REPO}:latest")
-echo "Image tags to push:"
-echo "  ${DOCKERHUB_REPO}:horizon-${RELEASE_VERSION}   (immutable, this 
release)"
-echo "  ${DOCKERHUB_REPO}:latest                      (moving — newest Horizon 
release)"
-
-if confirm "Build linux/amd64+arm64 and push to Docker Hub now? (emulated arch 
is slow)"; then
-    docker buildx build \
-        --builder "${BUILDER_NAME}" \
-        --platform linux/amd64,linux/arm64 \
-        --file "${PROJECT_DIR}/Dockerfile" \
-        --label 
"org.opencontainers.image.source=https://github.com/apache/skywalking-horizon-ui";
 \
-        --label "org.opencontainers.image.revision=$(git -C "${PROJECT_DIR}" 
rev-parse "${TAG}")" \
-        --label "org.opencontainers.image.version=${RELEASE_VERSION}" \
-        --label "org.opencontainers.image.title=Apache SkyWalking Horizon UI" \
-        --label "org.opencontainers.image.description=Next-generation web UI 
for Apache SkyWalking." \
-        --label "org.opencontainers.image.licenses=Apache-2.0" \
-        "${IMG_TAGS[@]}" \
-        --push \
-        "${BUILD_SRC}/src"
-    echo "Pushed multi-arch image to ${DOCKERHUB_REPO}."
-    echo "Verify:  docker buildx imagetools inspect 
${DOCKERHUB_REPO}:horizon-${RELEASE_VERSION}"
+# CI (.github/workflows/publish-image.yaml) mirrors the multi-arch image
+# to Docker Hub automatically on every `v*` tag push, so by the time
+# you're finalizing a passed vote this should already be live. We just
+# verify the two expected tags are present.
+#
+# Fallback: if CI didn't publish (workflow failed / secrets missing /
+# tag pushed before this workflow shipped), we fall back to the manual
+# `docker buildx imagetools create` mirror from the GHCR canonical tag
+# — same operation CI does, run locally. That needs Docker Hub push
+# rights on `apache/skywalking-ui`.
+DH_VERSION_TAG="${DOCKERHUB_REPO}:horizon-${RELEASE_VERSION}"
+DH_LATEST_TAG="${DOCKERHUB_REPO}:latest"
+GHCR_SRC="ghcr.io/apache/skywalking-horizon-ui:${RELEASE_VERSION}"
+
+echo "Expected on Docker Hub:"
+echo "  ${DH_VERSION_TAG}   (immutable, this release)"
+echo "  ${DH_LATEST_TAG}                      (moving — newest Horizon 
release)"
+
+if docker buildx imagetools inspect "${DH_VERSION_TAG}" >/dev/null 2>&1; then
+    echo "✓ ${DH_VERSION_TAG} already on Docker Hub — CI's publish-image 
mirror succeeded."
+    echo "  Verify:  docker buildx imagetools inspect ${DH_VERSION_TAG}"
 else
-    echo "Skipped Docker Hub push."
+    echo "✗ ${DH_VERSION_TAG} NOT on Docker Hub yet."
+    echo "  This is the expected outcome only if the publish-image workflow"
+    echo "  failed or didn't run on tag ${TAG}. Check:"
+    echo "    
https://github.com/apache/skywalking-horizon-ui/actions/workflows/publish-image.yaml";
+    if ! docker buildx imagetools inspect "${GHCR_SRC}" >/dev/null 2>&1; then
+        err "Source ${GHCR_SRC} not on GHCR either — CI didn't produce a 
multi-arch"
+        err "image to mirror. Re-run publish-image on ${TAG} from the Actions 
UI"
+        err "and then re-run this script."
+        exit 1
+    fi
+    if confirm "Fall back to a manual local mirror from ${GHCR_SRC}?"; then
+        docker buildx imagetools create \
+            -t "${DH_VERSION_TAG}" \
+            -t "${DH_LATEST_TAG}" \
+            "${GHCR_SRC}"
+        echo "Pushed multi-arch manifest to ${DOCKERHUB_REPO}."
+    else
+        echo "Skipped Docker Hub push — fix CI and re-run, OR run the 
imagetools"
+        echo "create manually:"
+        echo "  docker buildx imagetools create \\"
+        echo "    -t ${DH_VERSION_TAG} \\"
+        echo "    -t ${DH_LATEST_TAG} \\"
+        echo "    ${GHCR_SRC}"
+    fi
 fi
 
 # ========================== Done ==========================

Reply via email to