How to Parallelize a Monorepo Build in CI
A monorepo that builds every package in one serial job wastes the whole point of independent packages. Fanning them out cuts wall-clock time dramatically.
Parallelizing a monorepo means running independent packages concurrently while honoring the dependency graph. Combined with affected detection, only the changed slice of the graph runs at all.
1. Let the task runner parallelize
Nx and Turborepo schedule tasks across the dependency graph and run independent ones concurrently.
- run: npx turbo run build --concurrency=102. Fan out across jobs with a matrix
Generate a matrix of affected projects and run each in its own job for true machine-level parallelism.
jobs:
affected:
runs-on: ubuntu-latest
outputs:
projects: ${{ steps.list.outputs.projects }}
steps:
- id: list
run: echo "projects=$(npx nx show projects --affected --json)" >> $GITHUB_OUTPUT
build:
needs: affected
strategy:
matrix:
project: ${{ fromJson(needs.affected.outputs.projects) }}
runs-on: ubuntu-latest
steps:
- run: npx nx build ${{ matrix.project }}3. Respect the dependency graph
A package cannot build before its dependencies. Let the task runner topologically order tasks; do not hand-build a matrix that ignores the graph.
4. Scale runners without per-machine cost surprise
Fanning out multiplies concurrent runner usage. Latchkey managed runners are about 69 percent cheaper than hosted runners, so wide parallelism does not blow up the bill. Check the impact with the cost calculator at /learn/github-actions-cost-calculator.
Key takeaways
- Let the task runner schedule independent packages concurrently.
- Fan affected projects across a job matrix for machine-level parallelism.
- Honor the dependency graph; do not parallelize past it.