Skip to content

[Feature] Reusable build-image workflow #13

Description

@Gregory-Pereira

Reusable build-image workflow

Depends on: #11

Summary

Our goal here is to create a single re-usable build image workflow. We can call into this for when we setup our functional and regression tests to make sure code works in open PRs.

Modeled after llm-d's build-image.yaml.

What this workflow does

New file: .github/workflows/build-image.yaml with workflow_call and workflow_dispatch triggers.

Inputs and outputs

on:
  workflow_call:
    inputs:
      ref:
        description: "Git ref to build from"
        type: string
        default: ""
      force_build:
        description: "Build all images regardless of change detection"
        type: boolean
        default: false
    outputs:
      prediction_image:
        description: "Full image ref for the prediction server"
      training_image:
        description: "Full image ref for the training server"
      test_image:
        description: "Full image ref for the test runner"
      prediction_changed:
        description: "Whether prediction source changed"
      training_changed:
        description: "Whether training source changed"
  workflow_dispatch:
    inputs:
      force_build:
        description: "Build all images regardless of change detection"
        type: boolean
        default: false

workflow_dispatch lets maintainers kick off builds manually from the Actions UI.

Change detection

After the repo restructure, dorny/paths-filter can map changed paths to specific containers:

filters: |
  prediction:
    - 'src/llm_d_latency_predictor/prediction/**'
    - 'src/llm_d_latency_predictor/common/**'
    - 'Dockerfile-prediction'
    - 'pyproject.toml'
  training:
    - 'src/llm_d_latency_predictor/training/**'
    - 'src/llm_d_latency_predictor/common/**'
    - 'Dockerfile-training'
    - 'pyproject.toml'
  test:
    - 'src/llm_d_latency_predictor/test/**'
    - 'tests/**'
    - 'Dockerfile-test'
    - 'pyproject.toml'

When force_build is true, all three get built. Otherwise only containers whose source changed get rebuilt. The test container should also rebuild when prediction or training source changes, since tests run against those images.

This only works after the repo restructure. With the current flat layout, every Python change lands in src/llm_d_latency_predictor/ and there's no way to tell prediction changes from training changes.

Tagging

Use docker/metadata-action@v6:

- uses: docker/metadata-action@v6
  with:
    images: ghcr.io/llm-d/llm-d-latency-predictor-${{ matrix.service }}
    tags: |
      type=ref,event=pr
      type=raw,value=sha-${{ env.SHORT_SHA }}
      type=raw,value=main,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
      # ... and more

The conditional logic here is handled by the metadata action's event matching, not by us. Since this is a workflow_call workflow, it inherits the event context from whichever workflow called it. When the caller triggers on pull_request, the type=ref,event=pr rule fires and produces pr-<number>. When the caller triggers on push to main, that rule doesn't fire, but the main enable condition matches. The sha-<commit> tag fires in both cases. No if/else branching needed on our side.

Two contexts for tagging:

On open PRs, images get two tags: pr-<number> and sha-<commit>. The pr-<number> tag always points to the latest push on that PR, so someone who just wants to test the current state of the PR can pull by PR number. The sha-<commit> tag pins to a specific commit, useful when you need to match up exactly which code you're testing.

On merge to main, images get commit-<commit> and main. The main tag only updates on merges main, never on PRs and will basically be our version of a latest tag. This gives us a control mechanism: containers whose source didn't change in a PR can pull main (which is whatever's on main), while containers that did change get the PR-specific image. Downstream test workflows use the build outputs to pick the right tag for each container.

Build and push

docker/build-push-action@v7 with --push to ghcr.io for both amd64 and arm64. The multi-platform build logic, QEMU setup, and registry login that currently live in the composite action (.github/actions/docker-build-and-push) move here.

Concurrency

concurrency:
  group: build-image-${{ github.event.pull_request.number || github.ref }}-${{ github.sha }}
  cancel-in-progress: true

Prevents duplicate builds when someone pushes twice quickly.

Outputs

The workflow outputs full image refs for each container it built (e.g., ghcr.io/llm-d/llm-d-latency-predictor-prediction:pr-123).

Changes to existing files

  • .github/workflows/ci-pr-checks.yaml -- the container-build job is redundant once this workflow exists. Either remove it or keep it as a parallel fast-fail check. Lint stays.
  • .github/workflows/ci-release.yaml -- refactor to call build-image.yaml with force_build: true instead of inlining build logic through the composite action.
  • .github/actions/docker-build-and-push/action.yml -- can probably be retired once its logic lives in the reusable workflow. Check if anything else references it first.

Acceptance criteria

  • build-image.yaml exists with workflow_call and workflow_dispatch triggers
  • Uses dorny/paths-filter to map changed paths to containers, using the sub-package structure from the repo restructure
  • Supports force_build input to rebuild everything regardless of changes
  • Builds multi-arch images (amd64 + arm64)
  • PR builds push with pr-<number> and sha-<commit> tags
  • Main builds push with sha-<commit> and latest tags
  • latest tag only updates on main, never on PRs
  • Containers whose source didn't change in a PR fall back to latest (from main)
  • Outputs full image refs for calling workflows
  • ci-release.yaml calls build-image.yaml with force_build: true
  • Concurrency groups prevent duplicate builds on rapid pushes
  • Existing lint checks still work

Metadata

Metadata

Labels

CICDenhancementNew feature or requesttriage-acceptedIndicates maintainers have committed to accepting this change

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions