Upload Python Package workflow (PINTO0309/onnx2tf)
The Upload Python Package workflow from PINTO0309/onnx2tf, explained and optimized by Latchkey.
CI health: A - excellent
Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Upload Python Package workflow from the PINTO0309/onnx2tf repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Upload Python Package
on:
release:
types: [published]
env:
REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
DOCKER_REGISTRY_USER: pinto0309
IMAGE_NAME: ${{ github.repository }}
jobs:
build-python-dist:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78
with:
python-version: "3.12"
- name: Build
run: uv build
- name: Upload Python distributions
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: python-distributions
path: dist/*
if-no-files-found: error
retention-days: 1
pypi-publish:
needs: build-python-dist
runs-on: ubuntu-latest
permissions:
id-token: write
environment:
name: pypi
url: https://pypi.org/p/onnx2tf
steps:
- name: Download Python distributions
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
name: python-distributions
path: dist/
- name: Publish a Python distribution to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
with:
packages-dir: dist/
docker-deploy:
needs: pypi-publish
permissions:
contents: read
packages: write
strategy:
fail-fast: false # do not cancel even if any platform fails.
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- name: Git checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
- name: Enable buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Repository to lowercase
id: lower-repo-1
run: |
echo "repository=${GITHUB_REPOSITORY@L}" >> $GITHUB_OUTPUT
# For GitHub Container Registry
- name: Log in to GHCR
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Extract metadata (tags, labels) for GHCR
id: meta_gh
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf
with:
images: ${{ env.REGISTRY }}/${{ steps.lower-repo-1.outputs.repository }}
- name: Build and push by digest to GHCR
id: build_gh
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294
with:
context: .
platforms: ${{ matrix.platform }}
build-args: BUILD_ARCH=${{ matrix.platform }}
push: true
labels: ${{ steps.meta_gh.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ steps.lower-repo-1.outputs.repository }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha
cache-to: type=gha,mode=max
# For Docker Hub
- name: Log in to Docker Hub
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ env.DOCKER_REGISTRY_USER }}
password: ${{ secrets.DH_ACCESS_TOKEN }}
- name: Extract metadata (tags, labels) for Docker Hub
id: meta_dh
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-1.outputs.repository }}
- name: Build and push by digest to Docker Hub
id: build_dh
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294
with:
context: .
platforms: ${{ matrix.platform }}
build-args: BUILD_ARCH=${{ matrix.platform }}
push: true
labels: ${{ steps.meta_gh.outputs.labels }}
outputs: type=image,name=${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-1.outputs.repository }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Export digest
run: |
mkdir -p /tmp/digests/gh
digest="${{ steps.build_gh.outputs.digest }}"
touch "/tmp/digests/gh/${digest#sha256:}"
mkdir -p /tmp/digests/dh
digest="${{ steps.build_dh.outputs.digest }}"
touch "/tmp/digests/dh/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- docker-deploy
permissions:
contents: read
packages: write
steps:
- name: Download digests
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd
- name: Repository to lowercase
id: lower-repo-2
run: |
echo "repository=${GITHUB_REPOSITORY@L}" >> $GITHUB_OUTPUT
# For GitHub Container Registry
- name: Log in to GHCR
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Extract metadata (tags, labels) for GHCR
id: meta_gh
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf
with:
images: ${{ env.REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}
- name: Create manifest list and push to GHCR
working-directory: /tmp/digests/gh
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}@sha256:%s ' *)
- name: Inspect image on GHCR
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}:${{ steps.meta_gh.outputs.version }}
# For Docker Hub
- name: Log in to the Docker Hub
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ env.DOCKER_REGISTRY_USER }}
password: ${{ secrets.DH_ACCESS_TOKEN }}
- name: Extract metadata (tags, labels) for Docker Hub
id: meta_dh
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}
- name: Create manifest list and push to Docker Hub
working-directory: /tmp/digests/dh
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}@sha256:%s ' *)
- name: Inspect image on Docker Hub
run: |
docker buildx imagetools inspect ${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}:${{ steps.meta_dh.outputs.version }}
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Upload Python Package on: release: types: [published] env: REGISTRY: ghcr.io DOCKER_REGISTRY: docker.io DOCKER_REGISTRY_USER: pinto0309 IMAGE_NAME: ${{ github.repository }} jobs: build-python-dist: timeout-minutes: 30 runs-on: latchkey-small permissions: contents: read steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd - name: Install uv uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 with: python-version: "3.12" - name: Build run: uv build - name: Upload Python distributions uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: name: python-distributions path: dist/* if-no-files-found: error retention-days: 1 pypi-publish: timeout-minutes: 30 needs: build-python-dist runs-on: latchkey-small permissions: id-token: write environment: name: pypi url: https://pypi.org/p/onnx2tf steps: - name: Download Python distributions uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: python-distributions path: dist/ - name: Publish a Python distribution to PyPI uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e with: packages-dir: dist/ docker-deploy: timeout-minutes: 30 needs: pypi-publish permissions: contents: read packages: write strategy: fail-fast: false # do not cancel even if any platform fails. matrix: include: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm runs-on: ${{ matrix.runner }} steps: - name: Git checkout uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd - name: Enable buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd - name: Prepare run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Repository to lowercase id: lower-repo-1 run: | echo "repository=${GITHUB_REPOSITORY@L}" >> $GITHUB_OUTPUT # For GitHub Container Registry - name: Log in to GHCR uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ github.token }} - name: Extract metadata (tags, labels) for GHCR id: meta_gh uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf with: images: ${{ env.REGISTRY }}/${{ steps.lower-repo-1.outputs.repository }} - name: Build and push by digest to GHCR id: build_gh uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 with: context: . platforms: ${{ matrix.platform }} build-args: BUILD_ARCH=${{ matrix.platform }} push: true labels: ${{ steps.meta_gh.outputs.labels }} outputs: type=image,name=${{ env.REGISTRY }}/${{ steps.lower-repo-1.outputs.repository }},push-by-digest=true,name-canonical=true,push=true cache-from: type=gha cache-to: type=gha,mode=max # For Docker Hub - name: Log in to Docker Hub uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: registry: ${{ env.DOCKER_REGISTRY }} username: ${{ env.DOCKER_REGISTRY_USER }} password: ${{ secrets.DH_ACCESS_TOKEN }} - name: Extract metadata (tags, labels) for Docker Hub id: meta_dh uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf with: images: ${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-1.outputs.repository }} - name: Build and push by digest to Docker Hub id: build_dh uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 with: context: . platforms: ${{ matrix.platform }} build-args: BUILD_ARCH=${{ matrix.platform }} push: true labels: ${{ steps.meta_gh.outputs.labels }} outputs: type=image,name=${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-1.outputs.repository }},push-by-digest=true,name-canonical=true,push=true cache-from: type=gha cache-to: type=gha,mode=max - name: Export digest run: | mkdir -p /tmp/digests/gh digest="${{ steps.build_gh.outputs.digest }}" touch "/tmp/digests/gh/${digest#sha256:}" mkdir -p /tmp/digests/dh digest="${{ steps.build_dh.outputs.digest }}" touch "/tmp/digests/dh/${digest#sha256:}" - name: Upload digest uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 merge: timeout-minutes: 30 runs-on: latchkey-small needs: - docker-deploy permissions: contents: read packages: write steps: - name: Download digests uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: path: /tmp/digests pattern: digests-* merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd - name: Repository to lowercase id: lower-repo-2 run: | echo "repository=${GITHUB_REPOSITORY@L}" >> $GITHUB_OUTPUT # For GitHub Container Registry - name: Log in to GHCR uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ github.token }} - name: Extract metadata (tags, labels) for GHCR id: meta_gh uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf with: images: ${{ env.REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }} - name: Create manifest list and push to GHCR working-directory: /tmp/digests/gh run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}@sha256:%s ' *) - name: Inspect image on GHCR run: | docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}:${{ steps.meta_gh.outputs.version }} # For Docker Hub - name: Log in to the Docker Hub uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: registry: ${{ env.DOCKER_REGISTRY }} username: ${{ env.DOCKER_REGISTRY_USER }} password: ${{ secrets.DH_ACCESS_TOKEN }} - name: Extract metadata (tags, labels) for Docker Hub id: meta_dh uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf with: images: ${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }} - name: Create manifest list and push to Docker Hub working-directory: /tmp/digests/dh run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}@sha256:%s ' *) - name: Inspect image on Docker Hub run: | docker buildx imagetools inspect ${{ env.DOCKER_REGISTRY }}/${{ steps.lower-repo-2.outputs.repository }}:${{ steps.meta_dh.outputs.version }}
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
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:
- Container pulls and builds
This workflow runs 4 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.