name: Build & Push Docker Image on: workflow_dispatch: inputs: tag: description: "Image tag to publish under (e.g. 3.5.0 to rebuild a released version, or 3.5.0-fix.1 for a side-by-side). Leave blank for dev-." required: false type: string push_latest: description: "Also tag the resulting image as `latest` (use when rebuilding a stable release)." required: false type: boolean default: false push: tags: - "v*" env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} # Docker Hub mirror — same image, different registry. Credentials # come from the DOCKERHUB_USERNAME / DOCKERHUB_TOKEN secrets (token, # not account password). If either secret is empty, the Docker Hub # login step is skipped and only GHCR gets pushed. DOCKERHUB_IMAGE: caorushizi/mediago jobs: docker: name: Build and push multi-arch image runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout code uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Login to GitHub Container Registry uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # GitHub Actions forbids `secrets.*` inside `if:` expressions # directly — it fails with `Unrecognized named-value: 'secrets'`. # The blessed workaround: pull the secret through `env:` (which # IS allowed) inside a single check step, write a boolean to # step outputs, and have every downstream step guard on the # output instead of the raw secret. - name: Detect Docker Hub credentials id: dockerhub env: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} run: | if [ -n "$DOCKERHUB_USERNAME" ] && [ -n "$DOCKERHUB_TOKEN" ]; then echo "enabled=true" >> "$GITHUB_OUTPUT" else echo "enabled=false" >> "$GITHUB_OUTPUT" fi # Only log in (and later push) to Docker Hub when both secrets are # configured. Lets forks / trial runs without Docker Hub # credentials still build-and-push to GHCR. - name: Login to Docker Hub if: steps.dockerhub.outputs.enabled == 'true' uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} # Compose the list of target images based on which registries we # actually have credentials for. Without this, metadata-action # would always generate Docker Hub tags and build-push-action # would then fail with 401 on forks that haven't set the secrets. - name: Resolve image targets id: targets run: | { echo "images<> "$GITHUB_OUTPUT" - name: Docker meta id: meta uses: docker/metadata-action@v6 with: images: ${{ steps.targets.outputs.images }} tags: | # 1. Manual rebuild with an explicit tag (e.g. `3.5.0`, `3.5.0-fix.1`) type=raw,value=${{ inputs.tag }},enable=${{ inputs.tag != '' }} # 2. Tag push: v1.0.0 → 1.0.0 type=semver,pattern={{version}} # 3. `latest`: on stable semver tag pushes, or when the manual # rebuild explicitly asks for it type=raw,value=latest,enable=${{ (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'beta')) || inputs.push_latest }} # 4. Fallback for manual triggers with no custom tag type=raw,value=dev-${{ github.run_id }},enable=${{ !startsWith(github.ref, 'refs/tags/') && inputs.tag == '' }} - name: Build and push uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} # `metadata-action` already emits the standard OCI labels # (image.title / description / source / licenses / version / # revision / created). `revision` uses github.sha, so two # builds that re-use a tag are distinguishable via # `docker inspect`. labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - name: Build Summary run: | echo "### 🎉 Docker Image Published" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Registries:**" >> $GITHUB_STEP_SUMMARY echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY if [ "${{ steps.dockerhub.outputs.enabled }}" = "true" ]; then echo "- \`docker.io/${{ env.DOCKERHUB_IMAGE }}\`" >> $GITHUB_STEP_SUMMARY fi echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY echo "**Version:** \`${{ steps.meta.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Tags:**" >> $GITHUB_STEP_SUMMARY echo "${{ steps.meta.outputs.tags }}" | while read tag; do echo "- \`${tag}\`" >> $GITHUB_STEP_SUMMARY done echo "" >> $GITHUB_STEP_SUMMARY echo "**Pull:**" >> $GITHUB_STEP_SUMMARY echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY echo "docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}" >> $GITHUB_STEP_SUMMARY if [ "${{ steps.dockerhub.outputs.enabled }}" = "true" ]; then echo "docker pull ${{ env.DOCKERHUB_IMAGE }}:${{ steps.meta.outputs.version }}" >> $GITHUB_STEP_SUMMARY fi echo "\`\`\`" >> $GITHUB_STEP_SUMMARY