name: Docker Build and Publish on: push: branches: - next jobs: # Prepare metadata and tags for all builds prepare: runs-on: ubuntu-latest outputs: is_release: ${{ steps.check-release.outputs.is_release }} release_tag: ${{ steps.check-release.outputs.release_tag }} tags: ${{ steps.tags.outputs.tags }} image: ${{ steps.tags.outputs.image }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Check if this commit is a release id: check-release run: | git fetch --tags # Check if this is a release-please commit if git log -1 --pretty=%B | grep -q "release-please--branches--next"; then echo "This is a release commit, waiting for tag..." # Wait up to 60 seconds for release-please to create the tag for i in {1..12}; do sleep 5 git fetch --tags TAG=$(git tag --points-at HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || echo "") if [[ -n "$TAG" ]]; then echo "is_release=true" >> $GITHUB_OUTPUT echo "release_tag=$TAG" >> $GITHUB_OUTPUT echo "Found release tag: $TAG" exit 0 fi echo "Waiting for tag... ($i/12)" done echo "ERROR: Release commit but no tag found after 60s" exit 1 else # Regular commit, check for tag immediately TAG=$(git tag --points-at HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || echo "") if [[ -n "$TAG" ]]; then echo "is_release=true" >> $GITHUB_OUTPUT echo "release_tag=$TAG" >> $GITHUB_OUTPUT echo "This is a release: $TAG" else echo "is_release=false" >> $GITHUB_OUTPUT echo "This is a regular commit" fi fi - name: Compute tags id: tags run: | IMAGE="ghcr.io/${{ github.repository }}" if [[ "${{ steps.check-release.outputs.is_release }}" == "true" ]]; then # This is a release: use semver tags + latest TAG="${{ steps.check-release.outputs.release_tag }}" VERSION="${TAG#v}" MAJOR=$(echo "$VERSION" | cut -d. -f1) MINOR=$(echo "$VERSION" | cut -d. -f2) TAGS="${VERSION},${MAJOR}.${MINOR},${MAJOR},latest" echo "Building RELEASE with tags: $TAGS" else # Regular commit: use SHA tags only (no latest) SHORT_SHA="${GITHUB_SHA:0:7}" TAGS="sha-${SHORT_SHA}" echo "Building COMMIT with tags: $TAGS" fi echo "tags=$TAGS" >> $GITHUB_OUTPUT echo "image=$IMAGE" >> $GITHUB_OUTPUT # Build natively on each platform (faster, no QEMU emulation) build: needs: prepare runs-on: ${{ matrix.runner }} permissions: contents: read packages: write strategy: matrix: include: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Turborepo cache uses: actions/cache@v4 with: path: .turbo key: ${{ runner.os }}-${{ runner.arch }}-turbo-${{ github.sha }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}-turbo- - name: Free disk space run: | sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL docker system prune -af --volumes - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ needs.prepare.outputs.image }} - name: Build and push by digest id: build uses: docker/build-push-action@v6 with: context: . platforms: ${{ matrix.platform }} push: true labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ needs.prepare.outputs.image }},push-by-digest=true,name-canonical=true,push=true cache-from: type=gha,scope=${{ matrix.platform }} cache-to: type=gha,mode=max,scope=${{ matrix.platform }} build-args: | BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} VERSION=${{ github.ref_name }} REVISION=${{ github.sha }} - 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-${{ strategy.job-index }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 # Merge platform-specific images into multi-arch manifest merge: needs: [ prepare, build ] runs-on: ubuntu-latest permissions: contents: read packages: write attestations: write id-token: write steps: - name: Download digests uses: actions/download-artifact@v4 with: path: /tmp/digests pattern: digests-* merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Create manifest list and push working-directory: /tmp/digests run: | IMAGE="${{ needs.prepare.outputs.image }}" TAGS="${{ needs.prepare.outputs.tags }}" # Convert comma-separated tags to array and create docker tag arguments TAG_ARGS="" IFS=',' read -ra TAG_ARRAY <<< "$TAGS" for tag in "${TAG_ARRAY[@]}"; do TAG_ARGS="$TAG_ARGS -t ${IMAGE}:${tag}" done # Create and push manifest using all digests docker buildx imagetools create $TAG_ARGS \ $(printf '${{ needs.prepare.outputs.image }}@sha256:%s ' *) - name: Inspect image run: | IMAGE="${{ needs.prepare.outputs.image }}" TAGS="${{ needs.prepare.outputs.tags }}" FIRST_TAG=$(echo "$TAGS" | cut -d',' -f1) docker buildx imagetools inspect ${IMAGE}:${FIRST_TAG} - name: Summary run: | IMAGE="${{ needs.prepare.outputs.image }}" TAGS="${{ needs.prepare.outputs.tags }}" echo "## Docker Image Published 🚀" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [[ "${{ needs.prepare.outputs.is_release }}" == "true" ]]; then echo "**Type:** Release (${{ needs.prepare.outputs.release_tag }})" >> $GITHUB_STEP_SUMMARY else echo "**Type:** Commit (sha-${GITHUB_SHA:0:7})" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY echo "**Tags:**" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo "$TAGS" | tr ',' '\n' | sed "s|^|${IMAGE}:|" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY