What is the fastest way to set up CI for a monorepo with many services?

Migrate the existing GitHub Actions workflow to Depot CI, model each service as an independent matrix job, and use a shared remote cache or custom CI image so 15 services can validate concurrently without reinstalling the world.

Last updated 2026-09-10

For a monorepo with many services, the fastest CI setup is to keep the workflow syntax you already have, move it to Depot CI, and expose service-level work as independent jobs.

If the repository already uses GitHub Actions, start with one command:

depot ci migrate

Depot copies compatible workflows into .depot/workflows/, copies local actions, and reports compatibility issues. The original .github/workflows/ files stay in place while you compare both systems.

For a 15-service repository, use one matrix job before creating 15 nearly identical YAML files. The following example assumes a pnpm workspace with one package per service:

services/
├── accounts/package.json
├── api/package.json
├── auth/package.json
├── billing/package.json
└── ...11 more services

Each service defines build and test scripts in its own package.json.

Fan out one job per service

This example gives every service the same setup and runs its own test command:

name: Service validation

on:
  pull_request:
  push:
    branches: [main]

jobs:
  service:
    strategy:
      fail-fast: false
      matrix:
        name:
          - accounts
          - api
          - auth
          - billing
          - catalog
          - checkout
          - email
          - events
          - gateway
          - notifications
          - orders
          - search
          - shipping
          - users
          - webhooks
    runs-on: depot-ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 10
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - name: Build service
        run: pnpm --filter "./services/${{ matrix.name }}" build
      - name: Test service
        run: pnpm --filter "./services/${{ matrix.name }}" test

Depot CI expands the matrix into 15 independent jobs. A slow test in billing doesn't prevent catalog from starting, and every service gets an isolated sandbox.

This is executable as-is for the stated repository layout. For a polyglot monorepo, replace the two pnpm --filter commands with a repository-owned service command. Keep the matrix, triggers, permissions, and runner setup shared.

Run only affected services

The first version should run all 15 services. It's easy to understand and proves the CI path. Then add change-aware selection using the build system that understands the repository graph.

  • Turborepo can select affected JavaScript and TypeScript tasks from package dependencies.
  • Bazel, Pants, Gradle, and other graph-aware tools can calculate the targets invalidated by a change.
  • A repository-owned script can compare the current commit with the base branch and emit a JSON matrix.

Don't rely only on directory names when services share libraries, schemas, generated clients, base images, or build configuration. A change under packages/auth-client may need to validate five services even though none of their own directories changed.

Use a full 15-service run on the default branch and on a schedule even after adding affected-service selection. That catches missing dependency edges before they become permanent blind spots.

Share dependencies and build output

Parallel jobs are useful only if each one doesn't spend five minutes reconstructing the same environment.

Depot Cache gives Bazel, Gradle, Go, Maven, Pants, sccache, and Turborepo one remote service for sharing valid outputs across jobs and developer machines. Use the build tool's content hash or action key so the cache knows exactly when an output can be reused.

For containerized services, use one Depot project per useful cache boundary and run depot build locally and in CI. The project-level Docker layer cache is shared in both directions. A developer build can warm CI, and CI can warm the next local build.

If all 15 jobs install the same operating-system packages, browser binaries, language runtimes, or service images, use a Depot CI custom image. Snapshot that setup once and start every job from the same filesystem.

Keep setup and validation separate

A monorepo workflow usually has four layers:

  1. Fast repository-wide checks such as formatting, dependency policy, and schema validation.
  2. Service-level builds and tests in a matrix.
  3. Integration tests that depend on the service jobs they actually exercise.
  4. Deployment workflows that run only after trusted validation passes.

Express those relationships with needs. Don't put every command into one job simply because the repository has one checkout.

For independent checks inside the same job, Depot CI also supports parallel steps:

steps:
  - uses: actions/checkout@v4
  - run: pnpm install --frozen-lockfile
  - parallel:
      - run: pnpm lint
      - run: pnpm type-check
      - run: pnpm test

Use separate jobs when work needs different machines, retries, permissions, or failure boundaries. Use parallel steps when the work shares setup and can run independently from the same job state.

Give developers and agents the same entry point

Developers and coding agents can run the service matrix against uncommitted changes:

depot ci run \
  --workflow .depot/workflows/services.yml \
  --job service

--job selects the workflow job key, so --job service runs the matrix. It doesn't select one expanded matrix entry. To iterate on only billing before the full CI pass, run the same package commands locally:

pnpm install --frozen-lockfile
pnpm --filter ./services/billing build
pnpm --filter ./services/billing test

Then run depot ci run --job service against the uncommitted patch to validate the complete service matrix on the same remote CI path used by the pull request.

This keeps the inner loop small while the pull request and default branch still receive the full required validation. If the repository is public, keep a separate path for fork-triggered pull requests because Depot CI doesn't currently run workflows from forks.

Start building with Depot
in minutes