Skip to content
Latchkey

Build workflow (meltano/meltano)

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

C

CI health: C - fair

Point runs-on at Latchkey and get caching, 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: meltano/meltano.github/workflows/build.ymlLicense MITView source

What it does

This is the Build workflow from the meltano/meltano 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

workflow (.yml)
name: Build

on:
  push:
    branches-ignore:
    - 'gh-readonly-queue/**'
    paths:
    - .github/actions/docker-build-scan-push/action.yml
    - .github/workflows/build.yml
    - docker/meltano/Dockerfile
    - src/meltano/migrations/**
    - scripts/**
    - pyproject.toml
  workflow_dispatch:
  schedule:
  # Weekly on Sunday at 04:45 UTC
  - cron: '45 4 * * 0'
  release:
    types: [published]

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

permissions: {}

jobs:
  build:
    name: Wheel and SDist
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      name: ${{ steps.baipp.outputs.package_name }}
      version: ${{ steps.baipp.outputs.package_version }}

    steps:
    - name: Get latest release tag (re-roll)
      id: get-release-tag
      if: github.event_name == 'schedule'
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |  # zizmor: ignore[template-injection]
        tag=$(gh release list --repo ${{ github.repository }} --exclude-pre-releases --limit 1000 --json tagName --jq '.[].tagName' | sort -V | tail -1)
        if [[ -z "$tag" ]]; then
          echo "::error::No stable releases found; cannot determine tag for re-roll"
          exit 1
        fi
        echo "tag=$tag" >> "$GITHUB_OUTPUT"

    - name: Check out the repository
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      with:
        persist-credentials: false
        ref: ${{ steps.get-release-tag.outputs.tag || github.sha }}

    - name: Setup uv
      uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      with:
        enable-cache: false

    - name: Set up Python
      uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
      with:
        python-version-file: .python-version

    - name: Check version
      if: github.event_name == 'release' && github.event.action == 'published'
      run: |
        version=$(uvx --from toml-cli toml get --toml-path=pyproject.toml project.version)
        tag=$(echo "${GITHUB_REF}" | awk '{split($0,p,"/"); print p[3]}')
        if [ "v$version" != "$tag" ]; then echo "Release tag ('$tag') and package version ('v$version') do not match!"; exit 1; fi;

    - name: uv install
      # Required to run `alembic_freeze.py`
      run: |
        uv sync

    - name: Freeze DB
      run: |
        uv run scripts/alembic_freeze.py

    - name: Release Marker
      if: (github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'schedule'
      # The release marker differentiates installations 'in the wild' versus internal dev builds and tests
      run: touch src/meltano/core/tracking/.release_marker

    - name: Build distribution
      uses: hynek/build-and-inspect-python-package@d44ca7d91762de7a7d5436ddae667c6da6d1c3df # v2.18.0
      id: baipp

  build_docker:
    name: Docker Images (${{ matrix.slim && 'Slim' || 'Full' }}, Python ${{ matrix.python-version }})
    runs-on: ubuntu-latest
    needs: build
    permissions:
      actions: read # read the actions of the repo
      contents: read # read the contents of the repo
      packages: write # write the packages to the repo
      security-events: write # create security reports for the repo
    env:
      DEFAULT_PYTHON: "3.10"  # will be used in 'latest' images

    strategy:
      fail-fast: false
      matrix:
        python-version:
        - "3.10"
        - "3.11"
        - "3.12"
        - "3.13"
        - "3.14"
        slim:
        - false
        - true

    steps:
    - name: Set registry
      id: set-registry
      run: |
        if [[ "${GITHUB_EVENT_NAME}" == "release" && "${GITHUB_EVENT_ACTION}" == "published" ]] || [[ "${GITHUB_EVENT_NAME}" == "schedule" ]]; then
          echo "registry=docker.io" >> "$GITHUB_OUTPUT"
        else
          echo "registry=ghcr.io" >> "$GITHUB_OUTPUT"
        fi
      env:
        GITHUB_EVENT_ACTION: ${{ github.event.action }}
        GITHUB_EVENT_NAME: ${{ github.event_name }}

    - name: Set the workflow inputs
      id: set-inputs
      env:
        REGISTRY: ${{ steps.set-registry.outputs.registry }}
      # This step makes it so that the same workflow inputs can be accessed
      # regardless of what event triggered it.
      run: |
        echo "registry=$REGISTRY" >> "$GITHUB_OUTPUT"
    - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      with:
        persist-credentials: false
    - name: Setup uv
      uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      with:
        enable-cache: false

    - name: Get latest published release version
      id: get-latest-version
      if: steps.set-registry.outputs.registry == 'docker.io'
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |  # zizmor: ignore[template-injection]
        latest=$(gh release list --repo ${{ github.repository }} --exclude-pre-releases --limit 1000 --json tagName --jq '.[].tagName | ltrimstr("v")' | sort -V | tail -1)
        echo "version=$latest" >> "$GITHUB_OUTPUT"

    - name: Generate tags
      id: generate-tags
      run: |
        extra_args=()
        if [[ -n "$LATEST_VERSION" ]]; then
          extra_args+=(--latest-version "$LATEST_VERSION")
        fi
        ./scripts/generate_docker_tags.py \
          --git-sha "$GITHUB_SHA" \
          --package-version "$NEEDS_BUILD_OUTPUTS_VERSION" \
          --python-version "$PYTHON_VERSION" \
          --default-python "$DEFAULT_PYTHON" \
          --registry "$STEPS_SET_INPUTS_OUTPUTS_REGISTRY" \
          "${extra_args[@]}" \
          > tags
      env:
        DOCKER_TAGS_SLIM: ${{ matrix.slim && '1' || '0' }}
        GITHUB_SHA: ${{ github.sha }}
        NEEDS_BUILD_OUTPUTS_VERSION: ${{ needs.build.outputs.version }}
        PYTHON_VERSION: ${{ matrix.python-version }}
        STEPS_SET_INPUTS_OUTPUTS_REGISTRY: ${{ steps.set-inputs.outputs.registry }}
        LATEST_VERSION: ${{ steps.get-latest-version.outputs.version }}

    - name: Assemble image tags
      id: assemble-tags
      run: |
        echo "If this is not a dry run, the image will be published with the following tags:"
        cat tags

        echo 'IMAGE_TAGS<<EOF' >> "$GITHUB_ENV"
        echo "$(cat tags)" >> "$GITHUB_ENV"
        echo 'EOF' >> "$GITHUB_ENV"

    - name: Set registry username and password
      id: user-and-pass
      run: |
        if [[ "${STEPS_SET_INPUTS_OUTPUTS_REGISTRY}" == "ghcr.io" ]]; then
          echo "username=${GITHUB_ACTOR}" >> "$GITHUB_OUTPUT"
          echo "password=${GITHUB_TOKEN}" >> "$GITHUB_OUTPUT"
        fi
        if [[ "${STEPS_SET_INPUTS_OUTPUTS_REGISTRY}" == "docker.io" ]]; then
          echo "username=meltano" >> "$GITHUB_OUTPUT"
          echo "password=${DOCKERHUB_TOKEN}" >> "$GITHUB_OUTPUT"
        fi
      env:
        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        STEPS_SET_INPUTS_OUTPUTS_REGISTRY: ${{ steps.set-inputs.outputs.registry }}

    - name: Build, scan, then conditionally push the Docker image for a given Python version
      uses: ./.github/actions/docker-build-scan-push
      with:
        # Push to the container registry if
        # - The trigger is a push to the main branch
        # - The trigger is a workflow dispatch
        # - The trigger is a release and the action is 'published'
        # - The trigger is a weekly schedule (re-roll latest release with updated base image)
        push: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/main' ) || ( github.event_name == 'workflow_dispatch' ) || ( github.event_name == 'release' && github.event.action == 'published' ) || github.event_name == 'schedule' }}
        token: ${{ secrets.GITHUB_TOKEN }}
        tags: ${{ env.IMAGE_TAGS }}
        registry: ${{ steps.set-inputs.outputs.registry }}
        username: ${{ steps.user-and-pass.outputs.username }}
        password: ${{ steps.user-and-pass.outputs.password }}
        python-version: ${{ matrix.python-version }}
        slim: ${{ matrix.slim }}

  pypi_release:
    name: Publish to PyPI
    if: github.event_name == 'release' && github.event.action == 'published'
    needs: [build, build_docker]
    runs-on: ubuntu-latest
    permissions:
      id-token: write # Needed for OIDC PyPI publishing
    environment:
      name: publishing
      url: https://pypi.org/project/${{ needs.build.outputs.name }}/${{ needs.build.outputs.version }}

    steps:
    - name: Check out the repository
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      with:
        persist-credentials: false

    - name: Download artifacts
      uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
      with:
        name: Packages
        path: dist

    - name: Publish
      uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

name: Build
 
on:
  push:
    branches-ignore:
    - 'gh-readonly-queue/**'
    paths:
    - .github/actions/docker-build-scan-push/action.yml
    - .github/workflows/build.yml
    - docker/meltano/Dockerfile
    - src/meltano/migrations/**
    - scripts/**
    - pyproject.toml
  workflow_dispatch:
  schedule:
  # Weekly on Sunday at 04:45 UTC
  - cron: '45 4 * * 0'
  release:
    types: [published]
 
concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true
 
permissions: {}
 
jobs:
  build:
    timeout-minutes: 30
    name: Wheel and SDist
    runs-on: latchkey-small
    permissions:
      contents: read
    outputs:
      name: ${{ steps.baipp.outputs.package_name }}
      version: ${{ steps.baipp.outputs.package_version }}
 
    steps:
    - name: Get latest release tag (re-roll)
      id: get-release-tag
      if: github.event_name == 'schedule'
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |  # zizmor: ignore[template-injection]
        tag=$(gh release list --repo ${{ github.repository }} --exclude-pre-releases --limit 1000 --json tagName --jq '.[].tagName' | sort -V | tail -1)
        if [[ -z "$tag" ]]; then
          echo "::error::No stable releases found; cannot determine tag for re-roll"
          exit 1
        fi
        echo "tag=$tag" >> "$GITHUB_OUTPUT"
 
    - name: Check out the repository
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      with:
        persist-credentials: false
        ref: ${{ steps.get-release-tag.outputs.tag || github.sha }}
 
    - name: Setup uv
      uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      with:
        enable-cache: false
 
    - name: Set up Python
      uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
      with:
        cache: 'pip'
        python-version-file: .python-version
 
    - name: Check version
      if: github.event_name == 'release' && github.event.action == 'published'
      run: |
        version=$(uvx --from toml-cli toml get --toml-path=pyproject.toml project.version)
        tag=$(echo "${GITHUB_REF}" | awk '{split($0,p,"/"); print p[3]}')
        if [ "v$version" != "$tag" ]; then echo "Release tag ('$tag') and package version ('v$version') do not match!"; exit 1; fi;
 
    - name: uv install
      # Required to run `alembic_freeze.py`
      run: |
        uv sync
 
    - name: Freeze DB
      run: |
        uv run scripts/alembic_freeze.py
 
    - name: Release Marker
      if: (github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'schedule'
      # The release marker differentiates installations 'in the wild' versus internal dev builds and tests
      run: touch src/meltano/core/tracking/.release_marker
 
    - name: Build distribution
      uses: hynek/build-and-inspect-python-package@d44ca7d91762de7a7d5436ddae667c6da6d1c3df # v2.18.0
      id: baipp
 
  build_docker:
    timeout-minutes: 30
    name: Docker Images (${{ matrix.slim && 'Slim' || 'Full' }}, Python ${{ matrix.python-version }})
    runs-on: latchkey-small
    needs: build
    permissions:
      actions: read # read the actions of the repo
      contents: read # read the contents of the repo
      packages: write # write the packages to the repo
      security-events: write # create security reports for the repo
    env:
      DEFAULT_PYTHON: "3.10"  # will be used in 'latest' images
 
    strategy:
      fail-fast: false
      matrix:
        python-version:
        - "3.10"
        - "3.11"
        - "3.12"
        - "3.13"
        - "3.14"
        slim:
        - false
        - true
 
    steps:
    - name: Set registry
      id: set-registry
      run: |
        if [[ "${GITHUB_EVENT_NAME}" == "release" && "${GITHUB_EVENT_ACTION}" == "published" ]] || [[ "${GITHUB_EVENT_NAME}" == "schedule" ]]; then
          echo "registry=docker.io" >> "$GITHUB_OUTPUT"
        else
          echo "registry=ghcr.io" >> "$GITHUB_OUTPUT"
        fi
      env:
        GITHUB_EVENT_ACTION: ${{ github.event.action }}
        GITHUB_EVENT_NAME: ${{ github.event_name }}
 
    - name: Set the workflow inputs
      id: set-inputs
      env:
        REGISTRY: ${{ steps.set-registry.outputs.registry }}
      # This step makes it so that the same workflow inputs can be accessed
      # regardless of what event triggered it.
      run: |
        echo "registry=$REGISTRY" >> "$GITHUB_OUTPUT"
    - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      with:
        persist-credentials: false
    - name: Setup uv
      uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
      with:
        enable-cache: false
 
    - name: Get latest published release version
      id: get-latest-version
      if: steps.set-registry.outputs.registry == 'docker.io'
      env:
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |  # zizmor: ignore[template-injection]
        latest=$(gh release list --repo ${{ github.repository }} --exclude-pre-releases --limit 1000 --json tagName --jq '.[].tagName | ltrimstr("v")' | sort -V | tail -1)
        echo "version=$latest" >> "$GITHUB_OUTPUT"
 
    - name: Generate tags
      id: generate-tags
      run: |
        extra_args=()
        if [[ -n "$LATEST_VERSION" ]]; then
          extra_args+=(--latest-version "$LATEST_VERSION")
        fi
        ./scripts/generate_docker_tags.py \
          --git-sha "$GITHUB_SHA" \
          --package-version "$NEEDS_BUILD_OUTPUTS_VERSION" \
          --python-version "$PYTHON_VERSION" \
          --default-python "$DEFAULT_PYTHON" \
          --registry "$STEPS_SET_INPUTS_OUTPUTS_REGISTRY" \
          "${extra_args[@]}" \
          > tags
      env:
        DOCKER_TAGS_SLIM: ${{ matrix.slim && '1' || '0' }}
        GITHUB_SHA: ${{ github.sha }}
        NEEDS_BUILD_OUTPUTS_VERSION: ${{ needs.build.outputs.version }}
        PYTHON_VERSION: ${{ matrix.python-version }}
        STEPS_SET_INPUTS_OUTPUTS_REGISTRY: ${{ steps.set-inputs.outputs.registry }}
        LATEST_VERSION: ${{ steps.get-latest-version.outputs.version }}
 
    - name: Assemble image tags
      id: assemble-tags
      run: |
        echo "If this is not a dry run, the image will be published with the following tags:"
        cat tags
 
        echo 'IMAGE_TAGS<<EOF' >> "$GITHUB_ENV"
        echo "$(cat tags)" >> "$GITHUB_ENV"
        echo 'EOF' >> "$GITHUB_ENV"
 
    - name: Set registry username and password
      id: user-and-pass
      run: |
        if [[ "${STEPS_SET_INPUTS_OUTPUTS_REGISTRY}" == "ghcr.io" ]]; then
          echo "username=${GITHUB_ACTOR}" >> "$GITHUB_OUTPUT"
          echo "password=${GITHUB_TOKEN}" >> "$GITHUB_OUTPUT"
        fi
        if [[ "${STEPS_SET_INPUTS_OUTPUTS_REGISTRY}" == "docker.io" ]]; then
          echo "username=meltano" >> "$GITHUB_OUTPUT"
          echo "password=${DOCKERHUB_TOKEN}" >> "$GITHUB_OUTPUT"
        fi
      env:
        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        STEPS_SET_INPUTS_OUTPUTS_REGISTRY: ${{ steps.set-inputs.outputs.registry }}
 
    - name: Build, scan, then conditionally push the Docker image for a given Python version
      uses: ./.github/actions/docker-build-scan-push
      with:
        # Push to the container registry if
        # - The trigger is a push to the main branch
        # - The trigger is a workflow dispatch
        # - The trigger is a release and the action is 'published'
        # - The trigger is a weekly schedule (re-roll latest release with updated base image)
        push: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/main' ) || ( github.event_name == 'workflow_dispatch' ) || ( github.event_name == 'release' && github.event.action == 'published' ) || github.event_name == 'schedule' }}
        token: ${{ secrets.GITHUB_TOKEN }}
        tags: ${{ env.IMAGE_TAGS }}
        registry: ${{ steps.set-inputs.outputs.registry }}
        username: ${{ steps.user-and-pass.outputs.username }}
        password: ${{ steps.user-and-pass.outputs.password }}
        python-version: ${{ matrix.python-version }}
        slim: ${{ matrix.slim }}
 
  pypi_release:
    timeout-minutes: 30
    name: Publish to PyPI
    if: github.event_name == 'release' && github.event.action == 'published'
    needs: [build, build_docker]
    runs-on: latchkey-small
    permissions:
      id-token: write # Needed for OIDC PyPI publishing
    environment:
      name: publishing
      url: https://pypi.org/project/${{ needs.build.outputs.name }}/${{ needs.build.outputs.version }}
 
    steps:
    - name: Check out the repository
      uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
      with:
        persist-credentials: false
 
    - name: Download artifacts
      uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
      with:
        name: Packages
        path: dist
 
    - name: Publish
      uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
 

What changed

This workflow runs 3 jobs (12 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