Triggering Drone CI only for changed files in a monorepo is a performance optimization that avoids unnecessary builds and tests.

Let’s see it in action. Imagine you have a monorepo with a frontend directory and a backend directory. You want Drone CI to only run your frontend tests if a file in frontend has changed, and only run backend tests if a file in backend has changed.

Here’s a simplified .drone.yml that demonstrates this:

kind: pipeline
type: docker
name: default

trigger:
  branch:
    - main
  event:
    - push

steps:
  - name: frontend-tests
    image: node:18
    commands:
      - npm install
      - npm test
    when:
      filepath:
        - "frontend/**"

  - name: backend-tests
    image: python:3.10
    commands:
      - pip install -r requirements.txt
      - pytest
    when:
      filepath:
        - "backend/**"

In this example, the when clause on each step uses the filepath filter. Drone CI inspects the commit and checks which files were modified. If any file matching the frontend/** glob pattern was changed, the frontend-tests step will execute. Similarly, if a file matching backend/** was changed, backend-tests will run. If neither pattern matches, the respective step is skipped.

This pattern is incredibly powerful for managing large monorepos. Without it, every commit to any part of the monorepo would trigger a full build and test suite for all components, leading to wasted time and resources. By intelligently filtering which steps run based on file changes, you drastically reduce CI execution time and cost.

The core mechanism here is Drone’s filepath filter, which accepts glob patterns. When a commit is pushed, Drone determines the set of changed files and compares them against these patterns. If a match is found for a given step, that step is activated. You can use multiple patterns and even negate them with !. For instance, !docs/** would skip a step if only documentation files changed.

A common pitfall is using overly broad glob patterns. For example, if you have src/common/** and src/frontend/**, and a change occurs in src/common/utils.js which is imported by src/frontend/app.js, both common/** and frontend/** might be triggered if not carefully managed. It’s crucial to understand the dependencies within your monorepo and define your file paths accordingly to avoid redundant or incorrect builds. Think about the direct files changed, not just the files that might be affected by a change.

The filepath filter operates on the diff introduced by the commit. This means it looks at what actually changed in the HEAD commit relative to its parent. It doesn’t try to infer broader impacts beyond the immediate file modifications. This is a deliberate design choice for performance, but it means you need to be precise in your pattern matching.

When you’re working with a monorepo where different directories have entirely different build tools or dependencies (e.g., a Go service and a Node.js frontend), the filepath filter becomes essential for isolating those build processes. You can have separate build steps for each, ensuring only the relevant tools are invoked.

This approach is also fantastic for organizing your CI pipeline logically. Instead of one monolithic build script, you break it down into granular, context-aware steps. Each step is only concerned with its specific domain and only runs when that domain is affected by a code change.

The most surprising aspect for many is how granularly you can control this. It’s not just about top-level directories. You can go down to specific subdirectories or even file types within a directory if your monorepo structure warrants it. For example, src/api/**/*.go would trigger a Go API build step only if a .go file in the src/api directory changes.

The next logical step in optimizing monorepo CI is to consider caching strategies based on these changed files.

Want structured learning?

Take the full Drone course →