DevOps / GitHub Actions Interview Questions
How do you handle large monorepos with multiple services in GitHub Actions?
Large monorepos present two main problems: every commit triggers all CI jobs even when only one service changed, and a single workflow file becomes unmanageably large. The solution combines path filtering, dynamic matrices, and workflow decomposition.
Strategy 1 — Per-service workflow files with built-in path filters. Each service gets its own workflow file triggered only when its directory changes:
# .github/workflows/service-auth.yml on: push: paths: ['services/auth/**', '.github/workflows/service-auth.yml'] pull_request: paths: ['services/auth/**']
Strategy 2 — Centralised change detection with dorny/paths-filter. One workflow detects which services changed and gates subsequent jobs:
jobs: detect-changes: outputs: auth: ${{ steps.filter.outputs.auth }} payment: ${{ steps.filter.outputs.payment }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | auth: ['services/auth/**'] payment: ['services/payment/**'] build-auth: needs: detect-changes if: needs.detect-changes.outputs.auth == 'true' runs-on: ubuntu-latest steps: - run: ./build.sh auth
Strategy 3 — Dynamic matrix from changed services. A detection job produces a JSON array of changed service names, and a single build job consumes it as a matrix, avoiding N duplicated job blocks:
build: needs: detect-changes strategy: matrix: service: ${{ fromJSON(needs.detect-changes.outputs.changed-services) }} runs-on: ubuntu-latest steps: - run: ./build.sh ${{ matrix.service }}
Additional tips: use concurrency: groups scoped to service + branch to prevent duplicate runs, cache aggressively per service, and consider GitHub Actions' repository-level reusable workflows to share build logic across all services without per-repo duplication.
More Related questions...