name: Docker publish on: schedule: - cron: '00 0 * * *' push: branches: [ "main" ] paths: - 'backend/**' - 'frontend/**' - 'Dockerfile' - '.dockerignore' - '.github/workflows/docker-publish.yaml' ignore: - 'docs/**' tags: [ 'v*.*.*' ] pull_request: branches: [ "main" ] paths: - 'backend/**' - 'frontend/**' - 'Dockerfile' - '.dockerignore' - '.github/workflows/docker-publish.yaml' ignore: - 'docs/**' env: DOCKERHUB_REPO: sysadminsmedia/homebox GHCR_REPO: ghcr.io/${{ github.repository }} permissions: contents: read # Access to repository contents packages: write # Write access for pushing to GHCR id-token: write # Required for OIDC authentication (if used) attestations: write # Required for signing and attestation (if needed) jobs: build: runs-on: ubuntu-latest permissions: contents: read # Allows access to repository contents (read-only) packages: write # Allows pushing to GHCR id-token: write # Allows identity token write access for authentication attestations: write # Needed for signing and attestation (if required) strategy: fail-fast: false matrix: platform: - linux/amd64 - linux/arm64 - linux/arm/v7 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Prepare run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV branch=${{ github.event.pull_request.number || github.ref_name }} echo "BRANCH=${branch//\//-}" >> $GITHUB_ENV echo "DOCKERNAMES=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}" >> $GITHUB_ENV if [[ "${{ github.event_name }}" != "schedule" ]] || [[ "${{ github.ref }}" != refs/tags/* ]]; then echo "DOCKERNAMES=${{ env.GHCR_REPO }}" >> $GITHUB_ENV fi - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: | name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }} name=${{ env.GHCR_REPO }} - name: Login to Docker Hub uses: docker/login-action@v3 if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')) with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: ghcr.io/sysadminsmedia/binfmt:latest - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: driver-opts: | image=ghcr.io/sysadminsmedia/buildkit:latest - name: Build and push by digest id: build uses: docker/build-push-action@v6 with: platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,"name=${{ env.DOCKERNAMES }}",push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} cache-from: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR }}-${{ env.BRANCH }} cache-to: type=registry,ref=ghcr.io/sysadminsmedia/devcache:${{ env.PLATFORM_PAIR}}-${{ env.BRANCH }},mode=max,ignore-error=true build-args: | VERSION=${{ github.ref_name }} COMMIT=${{ github.sha }} provenance: mode=max sbom: true annotations: ${{ steps.meta.outputs.annotations }} - name: Attest platform-specific images uses: actions/attest-build-provenance@v1 if: github.event_name != 'pull_request' with: subject-name: ${{ env.GHCR_REPO }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true - name: Export digest run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest uses: actions/upload-artifact@v4 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 merge: if: github.event_name != 'pull_request' runs-on: ubuntu-latest permissions: contents: read # Allows access to repository contents (read-only) packages: write # Allows pushing to GHCR id-token: write # Allows identity token write access for authentication attestations: write # Needed for signing and attestation (if required) needs: - build steps: - name: Download digests uses: actions/download-artifact@v4 with: path: /tmp/digests pattern: digests-* merge-multiple: true - name: Login to Docker Hub uses: docker/login-action@v3 if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')) with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: driver-opts: | image=ghcr.io/sysadminsmedia/buildkit:master - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: | name=${{ env.DOCKERHUB_REPO }},enable=${{ github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/') }} name=${{ env.GHCR_REPO }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=schedule,pattern=nightly - name: Create manifest list and push GHCR id: push-ghcr working-directory: /tmp/digests run: | set -euo pipefail docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.GHCR_REPO }}@sha256:%s ' *) 2>&1 | tee /tmp/push-ghcr.out digest=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/push-ghcr.out | head -n1 || true) if [ -z "$digest" ]; then echo "No digest found in imagetools output:" cat /tmp/push-ghcr.out exit 1 fi echo "digest=$digest" >> $GITHUB_OUTPUT - name: Attest GHCR images uses: actions/attest-build-provenance@v1 if: github.event_name != 'pull_request' with: subject-name: ${{ env.GHCR_REPO }} subject-digest: ${{ steps.push-ghcr.outputs.digest }} push-to-registry: true - name: Create manifest list and push Dockerhub id: push-dockerhub working-directory: /tmp/digests if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')) run: | set -euo pipefail docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *) 2>&1 | tee /tmp/push-dockerhub.out digest=$(grep -oE 'sha256:[a-f0-9]{64}' /tmp/push-dockerhub.out | head -n1 || true) if [ -z "$digest" ]; then echo "No digest found in imagetools output:" cat /tmp/push-dockerhub.out exit 1 fi echo "digest=$digest" >> $GITHUB_OUTPUT - name: Attest Dockerhub images uses: actions/attest-build-provenance@v1 if: (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')) with: subject-name: docker.io/${{ env.DOCKERHUB_REPO }} subject-digest: ${{ steps.push-dockerhub.outputs.digest }} push-to-registry: true