Skip to content
Latchkey

Build And Push Docker Image workflow (Unstructured-IO/unstructured-api)

The Build And Push Docker Image workflow from Unstructured-IO/unstructured-api, explained and optimized by Latchkey.

C

CI health: C - fair

Run this on Latchkey for self-healing, caching, and up to 58% lower cost.

Grade your own workflow free or run it on Latchkey →
Source: Unstructured-IO/unstructured-api.github/workflows/docker-publish.ymlLicense Apache-2.0View source

What it does

This is the Build And Push Docker Image workflow from the Unstructured-IO/unstructured-api 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 Push Docker Image

on:
  push:
    branches:
      - main
  workflow_dispatch:

env:
  DOCKER_REPOSITORY: quay.io/unstructured-io/unstructured-api
  DOCKER_BUILD_REPOSITORY: quay.io/unstructured-io/build-unstructured-api
  PACKAGE: "unstructured-api"
  PIPELINE_FAMILY: "general"

jobs:
  set-short-sha:
    runs-on: opensource-linux-8core
    outputs:
      short_sha: ${{ steps.set_short_sha.outputs.short_sha }}
    steps:
      - name: Set Short SHA
        id: set_short_sha
        run: echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
  build-images:
    strategy:
      matrix:
        arch: ["arm64", "amd64"]
    runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'opensource-linux-8core' }}
    needs: set-short-sha
    env:
      SHORT_SHA: ${{ needs.set-short-sha.outputs.short_sha }}
      DOCKER_PLATFORM: linux/${{ matrix.arch }}
    steps:
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v4
      with:
        driver: docker
    - name: Checkout code
      uses: actions/checkout@v5
    - name: Read Python version from .python-version
      run: echo "PYTHON_VERSION=$(cat .python-version)" >> $GITHUB_ENV
    - name: Login to Quay.io
      uses: docker/login-action@v4
      with:
        registry: quay.io
        username: ${{ secrets.QUAY_IO_ROBOT_USERNAME }}
        password: ${{ secrets.QUAY_IO_ROBOT_TOKEN }}
    - name: Free up disk space
      run: |
        # Clear some space (https://github.com/actions/runner-images/issues/2840)
        echo "Disk usage before cleanup:"
        df -h

        # Remove unnecessary pre-installed software
        sudo rm -rf /usr/share/dotnet
        sudo rm -rf /opt/ghc
        sudo rm -rf /usr/local/share/boost
        sudo rm -rf /usr/local/lib/android
        sudo rm -rf /opt/hostedtoolcache/CodeQL
        sudo rm -rf /usr/local/.ghcup
        sudo rm -rf /usr/share/swift

        # Clean up docker to ensure we start fresh
        docker system prune -af --volumes

        echo "Disk usage after cleanup:"
        df -h
    - name: Build image
      run: |
        DOCKER_BUILDKIT=1 docker buildx build --load -f Dockerfile \
          --platform=$DOCKER_PLATFORM \
          --build-arg BUILDKIT_INLINE_CACHE=1 \
          --build-arg PIPELINE_PACKAGE=${{ env.PIPELINE_FAMILY }} \
          --provenance=false \
          --progress plain \
          --cache-from $DOCKER_BUILD_REPOSITORY:${{ matrix.arch }} \
          -t $DOCKER_BUILD_REPOSITORY:${{ matrix.arch }}-$SHORT_SHA .
    - name: Install uv
      uses: astral-sh/setup-uv@v7
      with:
        enable-cache: true
        cache-dependency-glob: "uv.lock"
    - name: Set up Python ${{ env.PYTHON_VERSION }}
      run: uv python install ${{ env.PYTHON_VERSION }}
    - name: Install test dependencies
      run: uv sync --group test --locked
    - name: Test image
      run: |
        export DOCKER_IMAGE="$DOCKER_BUILD_REPOSITORY:${{ matrix.arch }}-$SHORT_SHA"
        SKIP_INFERENCE_TESTS=true make docker-test
    - name: Push image
      run: |
        # write to the build repository to cache for the publish-images job
        docker push $DOCKER_BUILD_REPOSITORY:${{ matrix.arch }}-$SHORT_SHA
  publish-images:
    runs-on: opensource-linux-8core
    needs: [set-short-sha, build-images]
    env:
      SHORT_SHA: ${{ needs.set-short-sha.outputs.short_sha }}
    steps:
    - name: Checkout code
      uses: actions/checkout@v5
    - name: Set SHORT_SHA
      run: echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
    - name: Login to Quay.io
      uses: docker/login-action@v4
      with:
        registry: quay.io
        username: ${{ secrets.QUAY_IO_ROBOT_USERNAME }}
        password: ${{ secrets.QUAY_IO_ROBOT_TOKEN }}
    - name: Pull AMD image
      run: |
        docker pull $DOCKER_BUILD_REPOSITORY:amd64-$SHORT_SHA
    - name: Pull ARM image
      run: |
        docker pull $DOCKER_BUILD_REPOSITORY:arm64-$SHORT_SHA
    - name: Push AMD and ARM tags
      run: |
        # these are used to construct the final manifest but also cache-from in subsequent runs
        docker tag $DOCKER_BUILD_REPOSITORY:amd64-$SHORT_SHA $DOCKER_BUILD_REPOSITORY:amd64
        docker push $DOCKER_BUILD_REPOSITORY:amd64
        docker tag $DOCKER_BUILD_REPOSITORY:arm64-$SHORT_SHA $DOCKER_BUILD_REPOSITORY:arm64
        docker push $DOCKER_BUILD_REPOSITORY:arm64
    - name: Push multiarch manifest
      run: |
        VERSION=$(grep -oP '(?<=__version__ = ")[^"]+' prepline_general/api/__version__.py)
        docker buildx imagetools create \
          -t ${DOCKER_REPOSITORY}:latest \
          -t ${DOCKER_REPOSITORY}:$SHORT_SHA \
          -t ${DOCKER_REPOSITORY}:$VERSION \
          $DOCKER_BUILD_REPOSITORY:amd64 \
          $DOCKER_BUILD_REPOSITORY:arm64

The same workflow, on Latchkey

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

name: Build And Push Docker Image
 
on:
  push:
    branches:
      - main
  workflow_dispatch:
 
env:
  DOCKER_REPOSITORY: quay.io/unstructured-io/unstructured-api
  DOCKER_BUILD_REPOSITORY: quay.io/unstructured-io/build-unstructured-api
  PACKAGE: "unstructured-api"
  PIPELINE_FAMILY: "general"
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  set-short-sha:
    timeout-minutes: 30
    runs-on: opensource-linux-8core
    outputs:
      short_sha: ${{ steps.set_short_sha.outputs.short_sha }}
    steps:
      - name: Set Short SHA
        id: set_short_sha
        run: echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
  build-images:
    timeout-minutes: 30
    strategy:
      matrix:
        arch: ["arm64", "amd64"]
    runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'opensource-linux-8core' }}
    needs: set-short-sha
    env:
      SHORT_SHA: ${{ needs.set-short-sha.outputs.short_sha }}
      DOCKER_PLATFORM: linux/${{ matrix.arch }}
    steps:
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v4
      with:
        driver: docker
    - name: Checkout code
      uses: actions/checkout@v5
    - name: Read Python version from .python-version
      run: echo "PYTHON_VERSION=$(cat .python-version)" >> $GITHUB_ENV
    - name: Login to Quay.io
      uses: docker/login-action@v4
      with:
        registry: quay.io
        username: ${{ secrets.QUAY_IO_ROBOT_USERNAME }}
        password: ${{ secrets.QUAY_IO_ROBOT_TOKEN }}
    - name: Free up disk space
      run: |
        # Clear some space (https://github.com/actions/runner-images/issues/2840)
        echo "Disk usage before cleanup:"
        df -h
 
        # Remove unnecessary pre-installed software
        sudo rm -rf /usr/share/dotnet
        sudo rm -rf /opt/ghc
        sudo rm -rf /usr/local/share/boost
        sudo rm -rf /usr/local/lib/android
        sudo rm -rf /opt/hostedtoolcache/CodeQL
        sudo rm -rf /usr/local/.ghcup
        sudo rm -rf /usr/share/swift
 
        # Clean up docker to ensure we start fresh
        docker system prune -af --volumes
 
        echo "Disk usage after cleanup:"
        df -h
    - name: Build image
      run: |
        DOCKER_BUILDKIT=1 docker buildx build --load -f Dockerfile \
          --platform=$DOCKER_PLATFORM \
          --build-arg BUILDKIT_INLINE_CACHE=1 \
          --build-arg PIPELINE_PACKAGE=${{ env.PIPELINE_FAMILY }} \
          --provenance=false \
          --progress plain \
          --cache-from $DOCKER_BUILD_REPOSITORY:${{ matrix.arch }} \
          -t $DOCKER_BUILD_REPOSITORY:${{ matrix.arch }}-$SHORT_SHA .
    - name: Install uv
      uses: astral-sh/setup-uv@v7
      with:
        enable-cache: true
        cache-dependency-glob: "uv.lock"
    - name: Set up Python ${{ env.PYTHON_VERSION }}
      run: uv python install ${{ env.PYTHON_VERSION }}
    - name: Install test dependencies
      run: uv sync --group test --locked
    - name: Test image
      run: |
        export DOCKER_IMAGE="$DOCKER_BUILD_REPOSITORY:${{ matrix.arch }}-$SHORT_SHA"
        SKIP_INFERENCE_TESTS=true make docker-test
    - name: Push image
      run: |
        # write to the build repository to cache for the publish-images job
        docker push $DOCKER_BUILD_REPOSITORY:${{ matrix.arch }}-$SHORT_SHA
  publish-images:
    timeout-minutes: 30
    runs-on: opensource-linux-8core
    needs: [set-short-sha, build-images]
    env:
      SHORT_SHA: ${{ needs.set-short-sha.outputs.short_sha }}
    steps:
    - name: Checkout code
      uses: actions/checkout@v5
    - name: Set SHORT_SHA
      run: echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
    - name: Login to Quay.io
      uses: docker/login-action@v4
      with:
        registry: quay.io
        username: ${{ secrets.QUAY_IO_ROBOT_USERNAME }}
        password: ${{ secrets.QUAY_IO_ROBOT_TOKEN }}
    - name: Pull AMD image
      run: |
        docker pull $DOCKER_BUILD_REPOSITORY:amd64-$SHORT_SHA
    - name: Pull ARM image
      run: |
        docker pull $DOCKER_BUILD_REPOSITORY:arm64-$SHORT_SHA
    - name: Push AMD and ARM tags
      run: |
        # these are used to construct the final manifest but also cache-from in subsequent runs
        docker tag $DOCKER_BUILD_REPOSITORY:amd64-$SHORT_SHA $DOCKER_BUILD_REPOSITORY:amd64
        docker push $DOCKER_BUILD_REPOSITORY:amd64
        docker tag $DOCKER_BUILD_REPOSITORY:arm64-$SHORT_SHA $DOCKER_BUILD_REPOSITORY:arm64
        docker push $DOCKER_BUILD_REPOSITORY:arm64
    - name: Push multiarch manifest
      run: |
        VERSION=$(grep -oP '(?<=__version__ = ")[^"]+' prepline_general/api/__version__.py)
        docker buildx imagetools create \
          -t ${DOCKER_REPOSITORY}:latest \
          -t ${DOCKER_REPOSITORY}:$SHORT_SHA \
          -t ${DOCKER_REPOSITORY}:$VERSION \
          $DOCKER_BUILD_REPOSITORY:amd64 \
          $DOCKER_BUILD_REPOSITORY:arm64
 

What changed

3 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

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 3 jobs (4 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow