@@ -205,11 +205,6 @@ jobs:
205205 matrix : ${{ fromJSON(needs.discover.outputs.matrix_manifest) }}
206206 env :
207207 TAG4 : ${{ format('{0}-{1}-{2}', matrix.gemc_tag, matrix.image, matrix.image_tag) }}
208- # Docker Hub mirror target. Steps are skipped automatically when the
209- # DOCKERHUB_USERNAME secret is not configured (e.g. on forks).
210- # Override the namespace with the DOCKERHUB_REPO repo variable.
211- DOCKERHUB_USERNAME : ${{ secrets.DOCKERHUB_USERNAME }}
212- DOCKERHUB_REPO : ${{ vars.DOCKERHUB_REPO || 'docker.io/maureeungaro/gemc' }}
213208 steps :
214209 - name : Checkout repository
215210 uses : actions/checkout@v6
@@ -223,14 +218,6 @@ jobs:
223218 username : ${{ github.actor }}
224219 password : ${{ secrets.GITHUB_TOKEN }}
225220
226- - name : Log in to Docker Hub
227- if : ${{ env.DOCKERHUB_USERNAME != '' }}
228- uses : docker/login-action@v4
229- with :
230- registry : docker.io
231- username : ${{ secrets.DOCKERHUB_USERNAME }}
232- password : ${{ secrets.DOCKERHUB_TOKEN }}
233-
234221 - name : Set up Buildx
235222 uses : docker/setup-buildx-action@v4
236223
@@ -256,17 +243,6 @@ jobs:
256243 docker buildx imagetools create -t "$BASE_TAG" "$AMD_TAG"
257244 fi
258245
259- - name : Mirror manifest to Docker Hub
260- if : ${{ env.DOCKERHUB_USERNAME != '' }}
261- shell : bash
262- run : |
263- # Copy the already-built multi-arch manifest from GHCR to Docker Hub
264- # registry-to-registry (no pull, no rebuild).
265- SRC="${{ needs.discover.outputs.image }}:${{ env.TAG4 }}"
266- DST="${DOCKERHUB_REPO}:${{ env.TAG4 }}"
267- echo "Mirroring $SRC -> $DST"
268- docker buildx imagetools create -t "$DST" "$SRC"
269-
270246 - name : Download logs artifacts (all arches)
271247 if : ${{ always() }}
272248 uses : actions/download-artifact@v7
@@ -298,12 +274,85 @@ jobs:
298274 path : ${{ env.MANIFEST_SUMMARY_FILE }}
299275 if-no-files-found : warn
300276
301- # Push the Docker Hub overview README once per deploy. Skipped automatically
302- # when the DOCKERHUB_USERNAME secret is absent (e.g. on forks).
277+ # Mirror the GHCR multi-arch manifests to Docker Hub. This runs as a single
278+ # serial job (NOT inside the manifest matrix): all OS families target the same
279+ # Docker Hub repo, and concurrent blob uploads to one repo trip Docker Hub's
280+ # rate limits, so tags are copied one at a time with retries. The copy is
281+ # registry-to-registry (no pull, no rebuild). Skipped when the
282+ # DOCKERHUB_USERNAME secret is absent (e.g. on forks).
283+ dockerhub_mirror :
284+ name : Mirror images to Docker Hub
285+ needs : [ discover, manifest ]
286+ if : ${{ always() && needs.manifest.result == 'success' }}
287+ runs-on : ubuntu-latest
288+ env :
289+ DOCKERHUB_USERNAME : ${{ secrets.DOCKERHUB_USERNAME }}
290+ DOCKERHUB_REPO : ${{ vars.DOCKERHUB_REPO || 'docker.io/maureeungaro/gemc' }}
291+ steps :
292+ - name : Checkout repository
293+ uses : actions/checkout@v6
294+ with :
295+ ref : ${{ github.event.workflow_run.head_sha }}
296+
297+ - name : Log in to GHCR
298+ if : ${{ env.DOCKERHUB_USERNAME != '' }}
299+ uses : docker/login-action@v4
300+ with :
301+ registry : ghcr.io
302+ username : ${{ github.actor }}
303+ password : ${{ secrets.GITHUB_TOKEN }}
304+
305+ - name : Log in to Docker Hub
306+ if : ${{ env.DOCKERHUB_USERNAME != '' }}
307+ uses : docker/login-action@v4
308+ with :
309+ registry : docker.io
310+ username : ${{ secrets.DOCKERHUB_USERNAME }}
311+ password : ${{ secrets.DOCKERHUB_TOKEN }}
312+
313+ - name : Set up Buildx
314+ if : ${{ env.DOCKERHUB_USERNAME != '' }}
315+ uses : docker/setup-buildx-action@v4
316+
317+ - name : Mirror all tags (sequential, with retry)
318+ if : ${{ env.DOCKERHUB_USERNAME != '' }}
319+ shell : bash
320+ env :
321+ SRC_IMAGE : ${{ needs.discover.outputs.image }}
322+ run : |
323+ set -uo pipefail
324+ source ci/tags_config.sh
325+
326+ copy_with_retry() {
327+ local src=$1 dst=$2 n=0 max=5
328+ until docker buildx imagetools create -t "$dst" "$src"; do
329+ n=$((n + 1))
330+ if [ "$n" -ge "$max" ]; then
331+ echo "::error::failed to mirror $src -> $dst after $max attempts"
332+ return 1
333+ fi
334+ echo "retry $n/$max for $dst; backing off $((n * 15))s"
335+ sleep $((n * 15))
336+ done
337+ echo "mirrored $src -> $dst"
338+ }
339+
340+ rc=0
341+ for gemcv in $(get_gemc_tags); do
342+ for pair in "${OS_VERSIONS[@]}"; do
343+ os="${pair%%=*}"; ver="${pair#*=}"
344+ tag="${gemcv}-${os}-${ver}"
345+ copy_with_retry "${SRC_IMAGE}:${tag}" "${DOCKERHUB_REPO}:${tag}" || rc=1
346+ done
347+ done
348+ exit "$rc"
349+
350+ # Push the Docker Hub overview README once per deploy, after the images exist.
351+ # Skipped automatically when the DOCKERHUB_USERNAME secret is absent.
303352 dockerhub_readme :
304353 name : Update Docker Hub description
305- needs : manifest
306- if : ${{ always() && needs.manifest .result == 'success' }}
354+ needs : dockerhub_mirror
355+ if : ${{ always() && needs.dockerhub_mirror .result == 'success' }}
307356 runs-on : ubuntu-latest
308357 env :
309358 DOCKERHUB_USERNAME : ${{ secrets.DOCKERHUB_USERNAME }}
@@ -337,7 +386,7 @@ jobs:
337386 github.event.workflow_run.conclusion == 'success' &&
338387 github.event.workflow_run.event == 'push'
339388 }}
340- needs : [ build_arch, manifest ]
389+ needs : [ build_arch, manifest, dockerhub_mirror, dockerhub_readme ]
341390 runs-on : ubuntu-latest
342391 steps :
343392 - name : Fail if any required job failed
0 commit comments