Skip to content
Latchkey

Build and Publish workflow (quay/quay)

The Build and Publish workflow from quay/quay, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: quay/quay.github/workflows/build-and-publish.yamlLicense Apache-2.0View source

What it does

This is the Build and Publish workflow from the quay/quay repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
---
name: Build and Publish

on:
  push:
    branches:
      - redhat-** # IMPORTANT! this must match the jobs.build-and-publish.env.BRANCH_PREFIX (save the **).
  workflow_dispatch:
    # Allows you to run this workflow manually from the Actions tab
    inputs:
      tag:
        description: 'Tag to attach to image'
        required: true

permissions:
  contents: read

jobs:
  build-amd64-ppc64le-s390x:
    name: Build amd64, ppc64le, s390x images
    env:
      BRANCH_PREFIX: redhat- # IMPORTANT! this must match the .on.push.branches prefix!
      REGISTRY: ${{ secrets.REGISTRY || 'quay.io/projectquay' }}
      REPO_NAME: ${{ github.event.repository.name }}
      TAG_SUFFIX: -unstable
    runs-on: quay-001-large-ubuntu-24-x64
    outputs:
      digest: ${{ steps.docker_build.outputs.digest }}
      sg_rule_id: ${{ steps.sg-rule-id.outputs.rule_id }}
    steps:
      - name: Check out the repo
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Set version from branch name
        id: version-from-branch
        if: startsWith('env.BRANCH_PREFIX', env.GITHUB_REF)
        run: |
          BRANCH_NAME=${GITHUB_REF#refs/heads/}
          echo "version=${BRANCH_NAME/${{ env.BRANCH_PREFIX }}/}" >> $GITHUB_OUTPUT

      - name: install ibmcli and setup ibm login
        run: |
          curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
          ibmcloud login -q --apikey ${{ secrets.IBMCLOUD_API_KEY }} -r eu-gb
          ibmcloud plugin install vpc-infrastructure

      - name: Add rule to VPC
        id: sg-rule-id
        run: |
          cidr=$(dig +short myip.opendns.com @resolver1.opendns.com)
          echo $cidr
          SGRID=$(ibmcloud is security-group-rule-add --sg ${{ secrets.SG_ID }} --direction=inbound --protocol=tcp --port-min=22 --port-max=22 --remote=$cidr --output JSON | jq -r '.id')
          echo $SGRID
          echo "rule_id=${SGRID}" >> $GITHUB_OUTPUT

      - name: Setup SSH config for builders
        env:
          BUILDER_PPC64LE_SSH_CONFIG: ${{ secrets.BUILDER_PPC64LE_SSH_CONFIG }}
          BUILDER_PPC64LE_SSH_KEY: ${{ secrets.BUILDER_PPC64LE_SSH_KEY }}
          BUILDER_PPC64LE_SSH_KNOWN_HOSTS: ${{ secrets.BUILDER_PPC64LE_SSH_KNOWN_HOSTS }}
          BUILDER_S390X_SSH_HOST: ${{ secrets.BUILDER_S390X_SSH_HOST }}
          BUILDER_S390X_SSH_KEY: ${{ secrets.BUILDER_S390X_SSH_KEY }}
        run: |
          mkdir ~/.ssh
          chmod 700 ~/.ssh

          touch ~/.ssh/id_builder_ppc64le
          chmod 600 ~/.ssh/id_builder_ppc64le
          echo "$BUILDER_PPC64LE_SSH_KEY" >~/.ssh/id_builder_ppc64le

          touch ~/.ssh/id_builder_s390x
          chmod 600 ~/.ssh/id_builder_s390x
          echo "$BUILDER_S390X_SSH_KEY" > ~/.ssh/id_builder_s390x

          touch ~/.ssh/known_hosts
          chmod 600 ~/.ssh/known_hosts
          cat >~/.ssh/known_hosts <<END
          $BUILDER_PPC64LE_SSH_KNOWN_HOSTS
          END

          touch ~/.ssh/config
          chmod 600 ~/.ssh/config
          cat >~/.ssh/config <<END
          Host builder-ppc64le
            IdentityFile "~/.ssh/id_builder_ppc64le"
          $BUILDER_PPC64LE_SSH_CONFIG

          Host builder-s390x
            StrictHostKeyChecking no
            HostName $BUILDER_S390X_SSH_HOST
            User wfuser
            IdentityFile "~/.ssh/id_builder_s390x"
          END

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
        with:
          platforms: linux/amd64
          append: |
            - endpoint: ssh://builder-ppc64le
              platforms: linux/ppc64le
            - endpoint: ssh://builder-s390x
              platforms: linux/s390x

      - name: Login to Quay.io
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: quay.io
          username: ${{ secrets.QUAY_USER }}
          password: ${{ secrets.QUAY_TOKEN }}

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        env:
          TAG: ${{ steps.version-from-branch.outputs.version }}${{ env.TAG_SUFFIX }}
        with:
          platforms: linux/amd64,linux/ppc64le,linux/s390x
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.event.inputs.tag || env.TAG }}-amd64-ppc64le-s390x

  cleanup-sg-rule:
    name: Remove VPC SSH rule
    needs: build-amd64-ppc64le-s390x
    if: always() && needs.build-amd64-ppc64le-s390x.outputs.sg_rule_id != ''
    runs-on: ubuntu-latest
    steps:
      - name: Install ibmcli and login
        run: |
          curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
          ibmcloud login -q --apikey ${{ secrets.IBMCLOUD_API_KEY }} -r eu-gb
          ibmcloud plugin install vpc-infrastructure

      - name: Delete VPC security group rule
        env:
          SG_ID: ${{ secrets.SG_ID }}
          SG_RULE_ID: ${{ needs.build-amd64-ppc64le-s390x.outputs.sg_rule_id }}
        run: |
          ibmcloud is security-group-rule-delete "$SG_ID" "$SG_RULE_ID" -f

  build-arm64:
    name: Build ARM64 image
    runs-on: ubuntu-24.04-arm
    env:
      BRANCH_PREFIX: redhat-
      REGISTRY: ${{ secrets.REGISTRY || 'quay.io/projectquay' }}
      REPO_NAME: ${{ github.event.repository.name }}
      TAG_SUFFIX: -unstable
    outputs:
      digest: ${{ steps.docker_build.outputs.digest }}
    steps:
      - name: Check out the repo
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

      - name: Login to Quay.io
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: quay.io
          username: ${{ secrets.QUAY_USER }}
          password: ${{ secrets.QUAY_TOKEN }}

      - name: Set version from branch name
        id: version-from-branch
        run: |
          BRANCH_NAME=${GITHUB_REF#refs/heads/}
          echo "version=${BRANCH_NAME/${{ env.BRANCH_PREFIX }}/}" >> $GITHUB_OUTPUT

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        env:
          TAG: ${{ steps.version-from-branch.outputs.version }}${{ env.TAG_SUFFIX }}
        with:
          platforms: linux/arm64
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.event.inputs.tag || env.TAG }}-arm64

  create-manifest:
    name: Create multi-arch manifest
    needs: [build-amd64-ppc64le-s390x, build-arm64]
    runs-on: ubuntu-latest
    env:
      BRANCH_PREFIX: redhat-
      REGISTRY: ${{ secrets.REGISTRY || 'quay.io/projectquay' }}
      REPO_NAME: ${{ github.event.repository.name }}
      TAG_SUFFIX: -unstable
    steps:
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

      - name: Login to Quay.io
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: quay.io
          username: ${{ secrets.QUAY_USER }}
          password: ${{ secrets.QUAY_TOKEN }}

      - name: Set version
        id: version
        run: |
          BRANCH_NAME=${GITHUB_REF#refs/heads/}
          echo "branch_tag=${BRANCH_NAME/${{ env.BRANCH_PREFIX }}/}${{ env.TAG_SUFFIX }}" >> $GITHUB_OUTPUT

      - name: Create and push manifest
        env:
          TAG: ${{ github.event.inputs.tag || steps.version.outputs.branch_tag }}
          IMAGE: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}
          DIGEST_MULTI: ${{ needs.build-amd64-ppc64le-s390x.outputs.digest }}
          DIGEST_ARM64: ${{ needs.build-arm64.outputs.digest }}
        run: |
          # Combine the multi-arch image (amd64/ppc64le/s390x) with arm64 into final manifest
          docker buildx imagetools create -t "${IMAGE}:${TAG}" \
            "${IMAGE}@${DIGEST_MULTI}" \
            "${IMAGE}@${DIGEST_ARM64}"

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

---
name: Build and Publish
 
on:
  push:
    branches:
      - redhat-** # IMPORTANT! this must match the jobs.build-and-publish.env.BRANCH_PREFIX (save the **).
  workflow_dispatch:
    # Allows you to run this workflow manually from the Actions tab
    inputs:
      tag:
        description: 'Tag to attach to image'
        required: true
 
permissions:
  contents: read
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  build-amd64-ppc64le-s390x:
    timeout-minutes: 30
    name: Build amd64, ppc64le, s390x images
    env:
      BRANCH_PREFIX: redhat- # IMPORTANT! this must match the .on.push.branches prefix!
      REGISTRY: ${{ secrets.REGISTRY || 'quay.io/projectquay' }}
      REPO_NAME: ${{ github.event.repository.name }}
      TAG_SUFFIX: -unstable
    runs-on: quay-001-large-ubuntu-24-x64
    outputs:
      digest: ${{ steps.docker_build.outputs.digest }}
      sg_rule_id: ${{ steps.sg-rule-id.outputs.rule_id }}
    steps:
      - name: Check out the repo
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
 
      - name: Set version from branch name
        id: version-from-branch
        if: startsWith('env.BRANCH_PREFIX', env.GITHUB_REF)
        run: |
          BRANCH_NAME=${GITHUB_REF#refs/heads/}
          echo "version=${BRANCH_NAME/${{ env.BRANCH_PREFIX }}/}" >> $GITHUB_OUTPUT
 
      - name: install ibmcli and setup ibm login
        run: |
          curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
          ibmcloud login -q --apikey ${{ secrets.IBMCLOUD_API_KEY }} -r eu-gb
          ibmcloud plugin install vpc-infrastructure
 
      - name: Add rule to VPC
        id: sg-rule-id
        run: |
          cidr=$(dig +short myip.opendns.com @resolver1.opendns.com)
          echo $cidr
          SGRID=$(ibmcloud is security-group-rule-add --sg ${{ secrets.SG_ID }} --direction=inbound --protocol=tcp --port-min=22 --port-max=22 --remote=$cidr --output JSON | jq -r '.id')
          echo $SGRID
          echo "rule_id=${SGRID}" >> $GITHUB_OUTPUT
 
      - name: Setup SSH config for builders
        env:
          BUILDER_PPC64LE_SSH_CONFIG: ${{ secrets.BUILDER_PPC64LE_SSH_CONFIG }}
          BUILDER_PPC64LE_SSH_KEY: ${{ secrets.BUILDER_PPC64LE_SSH_KEY }}
          BUILDER_PPC64LE_SSH_KNOWN_HOSTS: ${{ secrets.BUILDER_PPC64LE_SSH_KNOWN_HOSTS }}
          BUILDER_S390X_SSH_HOST: ${{ secrets.BUILDER_S390X_SSH_HOST }}
          BUILDER_S390X_SSH_KEY: ${{ secrets.BUILDER_S390X_SSH_KEY }}
        run: |
          mkdir ~/.ssh
          chmod 700 ~/.ssh
 
          touch ~/.ssh/id_builder_ppc64le
          chmod 600 ~/.ssh/id_builder_ppc64le
          echo "$BUILDER_PPC64LE_SSH_KEY" >~/.ssh/id_builder_ppc64le
 
          touch ~/.ssh/id_builder_s390x
          chmod 600 ~/.ssh/id_builder_s390x
          echo "$BUILDER_S390X_SSH_KEY" > ~/.ssh/id_builder_s390x
 
          touch ~/.ssh/known_hosts
          chmod 600 ~/.ssh/known_hosts
          cat >~/.ssh/known_hosts <<END
          $BUILDER_PPC64LE_SSH_KNOWN_HOSTS
          END
 
          touch ~/.ssh/config
          chmod 600 ~/.ssh/config
          cat >~/.ssh/config <<END
          Host builder-ppc64le
            IdentityFile "~/.ssh/id_builder_ppc64le"
          $BUILDER_PPC64LE_SSH_CONFIG
 
          Host builder-s390x
            StrictHostKeyChecking no
            HostName $BUILDER_S390X_SSH_HOST
            User wfuser
            IdentityFile "~/.ssh/id_builder_s390x"
          END
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
        with:
          platforms: linux/amd64
          append: |
            - endpoint: ssh://builder-ppc64le
              platforms: linux/ppc64le
            - endpoint: ssh://builder-s390x
              platforms: linux/s390x
 
      - name: Login to Quay.io
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: quay.io
          username: ${{ secrets.QUAY_USER }}
          password: ${{ secrets.QUAY_TOKEN }}
 
      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        env:
          TAG: ${{ steps.version-from-branch.outputs.version }}${{ env.TAG_SUFFIX }}
        with:
          platforms: linux/amd64,linux/ppc64le,linux/s390x
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.event.inputs.tag || env.TAG }}-amd64-ppc64le-s390x
 
  cleanup-sg-rule:
    timeout-minutes: 30
    name: Remove VPC SSH rule
    needs: build-amd64-ppc64le-s390x
    if: always() && needs.build-amd64-ppc64le-s390x.outputs.sg_rule_id != ''
    runs-on: latchkey-small
    steps:
      - name: Install ibmcli and login
        run: |
          curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
          ibmcloud login -q --apikey ${{ secrets.IBMCLOUD_API_KEY }} -r eu-gb
          ibmcloud plugin install vpc-infrastructure
 
      - name: Delete VPC security group rule
        env:
          SG_ID: ${{ secrets.SG_ID }}
          SG_RULE_ID: ${{ needs.build-amd64-ppc64le-s390x.outputs.sg_rule_id }}
        run: |
          ibmcloud is security-group-rule-delete "$SG_ID" "$SG_RULE_ID" -f
 
  build-arm64:
    timeout-minutes: 30
    name: Build ARM64 image
    runs-on: latchkey-small-arm
    env:
      BRANCH_PREFIX: redhat-
      REGISTRY: ${{ secrets.REGISTRY || 'quay.io/projectquay' }}
      REPO_NAME: ${{ github.event.repository.name }}
      TAG_SUFFIX: -unstable
    outputs:
      digest: ${{ steps.docker_build.outputs.digest }}
    steps:
      - name: Check out the repo
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
 
      - name: Login to Quay.io
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: quay.io
          username: ${{ secrets.QUAY_USER }}
          password: ${{ secrets.QUAY_TOKEN }}
 
      - name: Set version from branch name
        id: version-from-branch
        run: |
          BRANCH_NAME=${GITHUB_REF#refs/heads/}
          echo "version=${BRANCH_NAME/${{ env.BRANCH_PREFIX }}/}" >> $GITHUB_OUTPUT
 
      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        env:
          TAG: ${{ steps.version-from-branch.outputs.version }}${{ env.TAG_SUFFIX }}
        with:
          platforms: linux/arm64
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.event.inputs.tag || env.TAG }}-arm64
 
  create-manifest:
    timeout-minutes: 30
    name: Create multi-arch manifest
    needs: [build-amd64-ppc64le-s390x, build-arm64]
    runs-on: latchkey-small
    env:
      BRANCH_PREFIX: redhat-
      REGISTRY: ${{ secrets.REGISTRY || 'quay.io/projectquay' }}
      REPO_NAME: ${{ github.event.repository.name }}
      TAG_SUFFIX: -unstable
    steps:
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
 
      - name: Login to Quay.io
        uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
        with:
          registry: quay.io
          username: ${{ secrets.QUAY_USER }}
          password: ${{ secrets.QUAY_TOKEN }}
 
      - name: Set version
        id: version
        run: |
          BRANCH_NAME=${GITHUB_REF#refs/heads/}
          echo "branch_tag=${BRANCH_NAME/${{ env.BRANCH_PREFIX }}/}${{ env.TAG_SUFFIX }}" >> $GITHUB_OUTPUT
 
      - name: Create and push manifest
        env:
          TAG: ${{ github.event.inputs.tag || steps.version.outputs.branch_tag }}
          IMAGE: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}
          DIGEST_MULTI: ${{ needs.build-amd64-ppc64le-s390x.outputs.digest }}
          DIGEST_ARM64: ${{ needs.build-arm64.outputs.digest }}
        run: |
          # Combine the multi-arch image (amd64/ppc64le/s390x) with arm64 into final manifest
          docker buildx imagetools create -t "${IMAGE}:${TAG}" \
            "${IMAGE}@${DIGEST_MULTI}" \
            "${IMAGE}@${DIGEST_ARM64}"
 

What changed

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 4 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow