Topic 8.2
CI for Monorepos: Path Filters, Affected Builds & Remote Caches
In one line
In a monorepo, rebuilding and testing everything on every change doesn't scale. Path filters, dependency-aware 'affected' builds, and shared remote build caches keep pipelines fast while preserving the benefits of one repository.
Think of it like this
A large office building. When one floor's lighting is changed, you don't inspect the wiring of every floor, only that floor and anything wired through it. Monorepo CI needs the same map of what depends on what.
Key ideas
- 01
PATH FILTERS (simplest): run the checkout-service workflow only when
services/checkout/**or sharedlibs/**change (on.push.pathsin GitHub Actions,rules: changes:in GitLab). Easy, but you must remember to list shared dependencies, or changes slip through untested. - 02
AFFECTED BUILDS (dependency graph): build tools know the dependency graph between projects and compute exactly what a change affects: Nx (
nx affected -t test), Turborepo, Bazel (bazel test //...with remote caching), Gradle (build cache + configuration on demand), Maven (-pl -am). Change a shared library and everything that depends on it is rebuilt and retested; nothing else is. - 03
REMOTE BUILD CACHE: if an output was already built for exactly the same inputs (by another developer or CI run), download it instead of rebuilding: Gradle build cache, Bazel remote cache, Nx Cloud/Turborepo remote cache. Combined with affected builds, typical PR times drop from tens of minutes to a few.
- 04
Other monorepo CI concerns: a merge queue to keep main green (Git course, Phase 8), clone strategies (partial/sparse clones: Git course, Phase 8), CODEOWNERS per directory, and one versioning and release policy per deployable (Topic 8.3).
Code & diagrams
on:
pull_request:
paths: ["services/checkout/**", "libs/**", "build.gradle.kts"]
jobs:
checkout:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 0 } # needed to compare with main
- uses: actions/setup-java@v5
with: { distribution: temurin, java-version: "21" }
- uses: gradle/actions/setup-gradle@v4 # Gradle build cache
- run: ./gradlew :services:checkout:check --build-cacheExplain it without notes
Why are path filters alone risky in a monorepo?
Practice
Your monorepo has 40 services; every PR runs all tests (50 minutes). What do you change first?
Trade-offs
- ↔
Graph-aware build tools (Bazel, Nx) give the fastest, most correct pipelines but add a learning curve and strict build definitions; path filters are simple but error-prone.
Done when you can
I can set up affected builds and a remote cache
My monorepo CI time grows with change size, not repo size