How to Build Only Changed Services in a Monorepo in CI
A detect job emits the list of changed services as JSON, and a matrix build job builds only those images.
Use dorny/paths-filter (or a git diff) to compute which service directories changed, emit them as a JSON array, then consume it with matrix: ${{ fromJSON(...) }} so each build job handles exactly one changed service.
Steps
- In a detect job, compute the changed service list and write JSON to
$GITHUB_OUTPUT. - Expose it as a job output.
- In the build job, set the matrix from
fromJSON(needs.detect.outputs.services).
Workflow
.github/workflows/docker.yml
jobs:
detect:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.f.outputs.changes }}
steps:
- uses: actions/checkout@v4
- id: f
uses: dorny/paths-filter@v3
with:
filters: |
api: 'services/api/**'
web: 'services/web/**'
build:
needs: detect
if: needs.detect.outputs.services != '[]'
strategy:
matrix:
service: ${{ fromJSON(needs.detect.outputs.services) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/build-push-action@v6
with:
context: services/${{ matrix.service }}
push: falseGotchas
- Guard the build job with
if: ... != '[]'so an empty change set produces zero jobs cleanly. - A shared base image change should rebuild every dependent service; add it as a path that maps to all services.
Related guides
How to Build a Matrix of Docker Image Variants in CIBuild several Docker image variants (different base versions or flavors) in parallel in CI using a build matr…
How to Tag a Docker Image by Git SHA and Semver in CIGenerate Docker tags from the commit SHA, branch, and semver tags in CI with docker/metadata-action, so every…