name: Build Docker Images on: pull_request: paths: - 'code/**' workflow_call: inputs: push_docker: description: 'Push Docker image to registry' type: boolean required: false default: true app_version: description: 'Application version' type: string required: false default: '' # Cancel in-progress runs for the same PR concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true permissions: contents: read packages: write env: REGISTRY_IMAGE: ghcr.io/cleanuparr/cleanuparr jobs: # Compute tags, version, and push decision for downstream jobs prepare: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository outputs: tags: ${{ steps.build-info.outputs.tags }} version: ${{ steps.build-info.outputs.version }} version_docker_tag: ${{ steps.build-info.outputs.version_docker_tag }} branch: ${{ steps.build-info.outputs.branch }} push: ${{ steps.build-info.outputs.push }} github_sha: ${{ github.sha }} steps: - name: Initialize build info id: build-info timeout-minutes: 1 run: | githubHeadRef="${{ github.head_ref }}" githubRef="${{ github.ref }}" inputVersion="${{ inputs.app_version }}" latestDockerTag="" versionDockerTag="" majorVersionDockerTag="" minorVersionDockerTag="" version="0.0.1" if [[ -n "$inputVersion" ]]; then # Version provided via input (manual release) branch="main" latestDockerTag="latest" versionDockerTag="$inputVersion" version="$inputVersion" # Extract major and minor versions for additional tags if [[ "$versionDockerTag" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then majorVersionDockerTag="${BASH_REMATCH[1]}" minorVersionDockerTag="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" fi elif [[ "$githubRef" =~ ^"refs/tags/" ]]; then # Tag push branch=${githubRef##*/} latestDockerTag="latest" versionDockerTag=${branch#v} version=${branch#v} # Extract major and minor versions for additional tags if [[ "$versionDockerTag" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then majorVersionDockerTag="${BASH_REMATCH[1]}" minorVersionDockerTag="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" fi else if [[ -z "$githubHeadRef" ]]; then # Main branch branch=${githubRef##*/} versionDockerTag="$branch" else # Pull request branch=$githubHeadRef versionDockerTag="$branch" fi fi githubTags="" if [ -n "$latestDockerTag" ]; then githubTags="$githubTags,$REGISTRY_IMAGE:$latestDockerTag" fi if [ -n "$versionDockerTag" ]; then githubTags="$githubTags,$REGISTRY_IMAGE:$versionDockerTag" fi if [ -n "$minorVersionDockerTag" ]; then githubTags="$githubTags,$REGISTRY_IMAGE:$minorVersionDockerTag" fi if [ -n "$majorVersionDockerTag" ]; then githubTags="$githubTags,$REGISTRY_IMAGE:$majorVersionDockerTag" fi githubTags="${githubTags#,}" # Determine push decision push="${{ github.event_name == 'pull_request' || inputs.push_docker == true }}" echo "tags=$githubTags" >> $GITHUB_OUTPUT echo "version=$version" >> $GITHUB_OUTPUT echo "version_docker_tag=$versionDockerTag" >> $GITHUB_OUTPUT echo "branch=$branch" >> $GITHUB_OUTPUT echo "push=$push" >> $GITHUB_OUTPUT # Build each platform in parallel build: runs-on: ubuntu-latest needs: [prepare] strategy: fail-fast: false matrix: platform: - linux/amd64 - linux/arm64 steps: - name: Prepare platform pair run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Get vault secrets uses: hashicorp/vault-action@v2 with: url: ${{ secrets.VAULT_HOST }} method: approle roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} secrets: secrets/data/github repo_readonly_pat | REPO_READONLY_PAT; secrets/data/github packages_pat | PACKAGES_PAT - name: Checkout target repository uses: actions/checkout@v4 timeout-minutes: 1 with: repository: ${{ github.repository }} ref: ${{ needs.prepare.outputs.branch }} token: ${{ env.REPO_READONLY_PAT }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 timeout-minutes: 5 - name: Login to GitHub Container Registry if: needs.prepare.outputs.push == 'true' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push by digest if: needs.prepare.outputs.push == 'true' id: build-push timeout-minutes: 30 uses: docker/build-push-action@v6 with: context: ${{ github.workspace }}/code file: ${{ github.workspace }}/code/Dockerfile provenance: false labels: | commit=sha-${{ needs.prepare.outputs.github_sha }} version=${{ needs.prepare.outputs.version_docker_tag }} build-args: | VERSION=${{ needs.prepare.outputs.version }} PACKAGES_USERNAME=${{ secrets.PACKAGES_USERNAME }} PACKAGES_PAT=${{ env.PACKAGES_PAT }} platforms: ${{ matrix.platform }} outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }} cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }},mode=max - name: Build (no push) if: needs.prepare.outputs.push != 'true' timeout-minutes: 30 uses: docker/build-push-action@v6 with: context: ${{ github.workspace }}/code file: ${{ github.workspace }}/code/Dockerfile provenance: false labels: | commit=sha-${{ needs.prepare.outputs.github_sha }} version=${{ needs.prepare.outputs.version_docker_tag }} build-args: | VERSION=${{ needs.prepare.outputs.version }} PACKAGES_USERNAME=${{ secrets.PACKAGES_USERNAME }} PACKAGES_PAT=${{ env.PACKAGES_PAT }} platforms: ${{ matrix.platform }} push: false cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }} cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }},mode=max - name: Export digest if: needs.prepare.outputs.push == 'true' run: | mkdir -p ${{ runner.temp }}/digests digest="${{ steps.build-push.outputs.digest }}" touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest if: needs.prepare.outputs.push == 'true' uses: actions/upload-artifact@v4 with: name: digests-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* if-no-files-found: error retention-days: 1 # Create multi-platform manifest and push with final tags merge: runs-on: ubuntu-latest needs: [prepare, build] if: needs.prepare.outputs.push == 'true' steps: - name: Download digests uses: actions/download-artifact@v4 with: path: ${{ runner.temp }}/digests pattern: digests-* merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Create manifest list and push timeout-minutes: 5 working-directory: ${{ runner.temp }}/digests run: | tags="${{ needs.prepare.outputs.tags }}" tag_args="" IFS=',' read -ra TAG_ARRAY <<< "$tags" for tag in "${TAG_ARRAY[@]}"; do tag=$(echo "$tag" | xargs) if [ -n "$tag" ]; then tag_args="$tag_args -t $tag" fi done docker buildx imagetools create $tag_args \ $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - name: Inspect image run: | tags="${{ needs.prepare.outputs.tags }}" first_tag=$(echo "$tags" | tr ',' '\n' | grep -v '^$' | head -1 | xargs) docker buildx imagetools inspect "$first_tag"