From 1f19fbb1b205b2b60fb70a9157e71dc2aa2d700d Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:26:06 +0200 Subject: [PATCH] ci(docker): mirror published images to Docker Hub Add picpeak/backend + picpeak/frontend on Docker Hub alongside GHCR. The merge jobs already assemble the multi-arch manifest from the per-arch GHCR digests via 'imagetools create'; adding Docker Hub to metadata-action's images list + a Docker Hub login makes the same command push the manifest to both registries (blobs copied from GHCR). No change to the build-by-digest jobs. Full tag parity (main, stable, latest, semver, sha). Gated on DOCKERHUB_ENABLED (github.repository == PicPeak/picpeak) so forks stay GHCR-only and keep building. Requires repo secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN. --- .github/workflows/docker-build.yml | 93 ++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 50fe78b3..10fe73d2 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -95,6 +95,15 @@ jobs: repo_lc="${GITHUB_REPOSITORY,,}" echo "BACKEND_IMAGE_NAME=${repo_lc}/backend" >> "$GITHUB_ENV" echo "FRONTEND_IMAGE_NAME=${repo_lc}/frontend" >> "$GITHUB_ENV" + # Mirror manifests to Docker Hub (picpeak/{backend,frontend}) only on the + # canonical org repo, where the DOCKERHUB_* secrets live. Forks (and any + # other owner) fall back to GHCR-only — the Docker Hub image line and login + # are gated on this flag so their builds keep working unchanged. + if [[ "$GITHUB_REPOSITORY" == "PicPeak/picpeak" ]]; then + echo "DOCKERHUB_ENABLED=true" >> "$GITHUB_ENV" + else + echo "DOCKERHUB_ENABLED=false" >> "$GITHUB_ENV" + fi - name: Prepare platform pair run: | @@ -233,6 +242,15 @@ jobs: repo_lc="${GITHUB_REPOSITORY,,}" echo "BACKEND_IMAGE_NAME=${repo_lc}/backend" >> "$GITHUB_ENV" echo "FRONTEND_IMAGE_NAME=${repo_lc}/frontend" >> "$GITHUB_ENV" + # Mirror manifests to Docker Hub (picpeak/{backend,frontend}) only on the + # canonical org repo, where the DOCKERHUB_* secrets live. Forks (and any + # other owner) fall back to GHCR-only — the Docker Hub image line and login + # are gated on this flag so their builds keep working unchanged. + if [[ "$GITHUB_REPOSITORY" == "PicPeak/picpeak" ]]; then + echo "DOCKERHUB_ENABLED=true" >> "$GITHUB_ENV" + else + echo "DOCKERHUB_ENABLED=false" >> "$GITHUB_ENV" + fi - name: Download digest artifacts uses: actions/download-artifact@v4 @@ -266,11 +284,24 @@ jobs: echo "is_prerelease=false" >> $GITHUB_OUTPUT fi + - name: Log in to Docker Hub + if: env.DOCKERHUB_ENABLED == 'true' + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Extract metadata for Backend id: meta-backend uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }} + # GHCR always; Docker Hub (picpeak/backend) added on the canonical repo so + # the same tag scheme is mirrored to both registries. metadata-action drops + # the blank second line on forks → GHCR-only there. + images: | + ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }} + ${{ env.DOCKERHUB_ENABLED == 'true' && 'docker.io/picpeak/backend' || '' }} labels: | org.opencontainers.image.title=PicPeak Backend org.opencontainers.image.description=PicPeak photo sharing platform backend service @@ -301,10 +332,15 @@ jobs: docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf "${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}@sha256:%s " *) - - name: Inspect manifest + - name: Inspect manifest (GHCR) run: | docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ steps.meta-backend.outputs.version }} + - name: Inspect manifest (Docker Hub) + if: env.DOCKERHUB_ENABLED == 'true' + run: | + docker buildx imagetools inspect docker.io/picpeak/backend:${{ steps.meta-backend.outputs.version }} + # ----------------------------------------------------------------------------- # Frontend: per-arch build, then merge into a multi-arch manifest # ----------------------------------------------------------------------------- @@ -334,6 +370,15 @@ jobs: repo_lc="${GITHUB_REPOSITORY,,}" echo "BACKEND_IMAGE_NAME=${repo_lc}/backend" >> "$GITHUB_ENV" echo "FRONTEND_IMAGE_NAME=${repo_lc}/frontend" >> "$GITHUB_ENV" + # Mirror manifests to Docker Hub (picpeak/{backend,frontend}) only on the + # canonical org repo, where the DOCKERHUB_* secrets live. Forks (and any + # other owner) fall back to GHCR-only — the Docker Hub image line and login + # are gated on this flag so their builds keep working unchanged. + if [[ "$GITHUB_REPOSITORY" == "PicPeak/picpeak" ]]; then + echo "DOCKERHUB_ENABLED=true" >> "$GITHUB_ENV" + else + echo "DOCKERHUB_ENABLED=false" >> "$GITHUB_ENV" + fi - name: Prepare platform pair run: | @@ -453,6 +498,15 @@ jobs: repo_lc="${GITHUB_REPOSITORY,,}" echo "BACKEND_IMAGE_NAME=${repo_lc}/backend" >> "$GITHUB_ENV" echo "FRONTEND_IMAGE_NAME=${repo_lc}/frontend" >> "$GITHUB_ENV" + # Mirror manifests to Docker Hub (picpeak/{backend,frontend}) only on the + # canonical org repo, where the DOCKERHUB_* secrets live. Forks (and any + # other owner) fall back to GHCR-only — the Docker Hub image line and login + # are gated on this flag so their builds keep working unchanged. + if [[ "$GITHUB_REPOSITORY" == "PicPeak/picpeak" ]]; then + echo "DOCKERHUB_ENABLED=true" >> "$GITHUB_ENV" + else + echo "DOCKERHUB_ENABLED=false" >> "$GITHUB_ENV" + fi - name: Download digest artifacts uses: actions/download-artifact@v4 @@ -486,11 +540,24 @@ jobs: echo "is_prerelease=false" >> $GITHUB_OUTPUT fi + - name: Log in to Docker Hub + if: env.DOCKERHUB_ENABLED == 'true' + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Extract metadata for Frontend id: meta-frontend uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }} + # GHCR always; Docker Hub (picpeak/frontend) added on the canonical repo so + # the same tag scheme is mirrored to both registries. metadata-action drops + # the blank second line on forks → GHCR-only there. + images: | + ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }} + ${{ env.DOCKERHUB_ENABLED == 'true' && 'docker.io/picpeak/frontend' || '' }} labels: | org.opencontainers.image.title=PicPeak Frontend org.opencontainers.image.description=PicPeak photo sharing platform frontend application @@ -521,10 +588,15 @@ jobs: docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf "${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}@sha256:%s " *) - - name: Inspect manifest + - name: Inspect manifest (GHCR) run: | docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ steps.meta-frontend.outputs.version }} + - name: Inspect manifest (Docker Hub) + if: env.DOCKERHUB_ENABLED == 'true' + run: | + docker buildx imagetools inspect docker.io/picpeak/frontend:${{ steps.meta-frontend.outputs.version }} + summary: needs: [build-backend, merge-backend, build-frontend, merge-frontend] if: always() @@ -538,6 +610,15 @@ jobs: repo_lc="${GITHUB_REPOSITORY,,}" echo "BACKEND_IMAGE_NAME=${repo_lc}/backend" >> "$GITHUB_ENV" echo "FRONTEND_IMAGE_NAME=${repo_lc}/frontend" >> "$GITHUB_ENV" + # Mirror manifests to Docker Hub (picpeak/{backend,frontend}) only on the + # canonical org repo, where the DOCKERHUB_* secrets live. Forks (and any + # other owner) fall back to GHCR-only — the Docker Hub image line and login + # are gated on this flag so their builds keep working unchanged. + if [[ "$GITHUB_REPOSITORY" == "PicPeak/picpeak" ]]; then + echo "DOCKERHUB_ENABLED=true" >> "$GITHUB_ENV" + else + echo "DOCKERHUB_ENABLED=false" >> "$GITHUB_ENV" + fi - name: Build Summary run: | @@ -576,6 +657,10 @@ jobs: echo "### 📦 Images" >> $GITHUB_STEP_SUMMARY echo "- Backend: \`${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY echo "- Frontend: \`${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY + if [[ "$DOCKERHUB_ENABLED" == "true" ]]; then + echo "- Backend (Docker Hub): \`docker.io/picpeak/backend\`" >> $GITHUB_STEP_SUMMARY + echo "- Frontend (Docker Hub): \`docker.io/picpeak/frontend\`" >> $GITHUB_STEP_SUMMARY + fi echo "" >> $GITHUB_STEP_SUMMARY echo "### 🏗️ Architectures" >> $GITHUB_STEP_SUMMARY