Best practices for target-based triggers with Bitrise

If you've ever found yourself tangled up in the complexities of setting up CI/CD pipelines, you're familiar with the challenge of mapping code events with build triggers. At times, you may wish to initiate a few checks, while at other times, you prefer to execute several checks at once. It can be slow and, at times, frustrating to ensure that only those CI checks are triggered that are relevant to the code change.

A better, more scalable approach? Target-based triggering.

What is target-based triggering?

In a nutshell, it’s a smarter way to run your builds based on specific code events happening in your Git repositories. Whether it's a code push, a pull request, or even a comment on a pull request, you can now trigger multiple workflows or pipelines independently, based on conditions you define. This lets you run all the relevant parts of your CI together based on the scope of code changes. For DevOps teams juggling multiple platforms (like iOS and Android) or large monorepos, this targeted approach brings greater control and efficiency to your CI/CD process.

What’s new in Bitrise

New “triggers” property for workflows or pipelines: Workflows and pipelines now include a dedicated triggers property, which organizes triggers under specific event types. For example

1workflows: 
2  ui_tests:
3    triggers:

Example pull request trigger:

1workflows: 
2   ui_tests:
3    triggers:
4      pull_request:
5      - target_branch: "main"
6      - comment: "[workflow: ui_tests]"

The above example shows two PR triggers for the ui_tests workflow: one that targets the main branch and another for a PR comment [workflow: ui_tests].”

You can check out detailed documentation here.

You can also set up triggers via the UI by navigating to any workflow or a graph pipeline. The right-hand panel will display a “Triggers” tab. Add Push, Pull request, or Tag triggers for the selected workflow or graph pipeline. Check out documentation here.

Benefits of target-based triggering

Let's dive into the benefits that make target-based triggering a must-have in your CI/CD toolkit:

  • Smart multitriggering of workflows or pipelines: You can now independently trigger multiple workflows or pipelines based on very specific conditions, enabling more granular control over build processes. You will no longer need to write complex config to achieve this.
  • Simplified configuration: Trigger-related configuration is now cleaner and more intuitive. Workflow/pipeline context is inherent, so you no longer need to specify it within each trigger. Field names are also more straightforward – we removed event-specific prefixes from filter field names, such as pull_request_ or push_, simplifying them to branch, commit_message, etc., since they are already within a context-specific section.
  • Better support for complex projects: Particularly beneficial for projects with monorepos, the new model allows for precise targeting of builds based on changes to specific parts of the codebase, such as different platforms or shared components.
  • Increased efficiency: Configuring multiple workflows from a single Git event can save time and reduce maintenance efforts. This eliminates duplicate steps and allows you to focus on improving your codebase.

Some real-world examples of multitriggering (where target-based triggering wins)

Example 1: Triggering multiple pipelines from the same branch

Let's break down the scenario:

You have three pipelines:

  • pipeline-tests: Runs tests (triggered by pushes to feature branches).
  • pipeline-builds: Creates builds (triggered by pull requests).
  • pipeline-release: Handles releases (triggered by release tags).

Now, when you push to the release branch, you want all three pipelines to run independently. This allows you to test, build, and deploy in one smooth, automated process. Previously, this was difficult, often requiring you to merge the pipelines into one, which, as we discussed earlier, leads to complexity and inflexibility.

The good news? Bitrise now allows you to easily configure this. You can define separate triggers for each pipeline, even if they all point to the same branch. This means you can have your pipeline-tests, pipeline-builds, and pipeline-release pipelines all triggered simultaneously by a single push to the release branch. This unlocks powerful CI/CD workflows, enabling parallel processing, customized workflows, and independent pipeline management. No more monolithic pipelines!

The YAML configuration for this is surprisingly simple. Let's take a look:

1pipelines:
2  pipeline-tests:
3    triggers:
4      push:
5      - branch: "feature/*"
6      - branch: "release"
7    stages:
8    - 
9
10  pipeline-builds:
11    triggers:
12      pull_request:
13      - target_branch: "*"
14      push:
15      - branch: release
16    stages:
17    -
18
19  pipeline-releases:
20    triggers:
21      push:
22      - branch: release
23      tag:
24      - name: "*"
25    stages:
26    - 

Example 2: Selective multi-triggers for libraries in a monorepo

Let's say you have a monorepo with two libraries, library-a and library-b, each with its own pipeline: pipeline-a for library-a and pipeline-b for library-b. Seems simple, right?  But how do you ensure the right pipeline runs when changes are made? Let's look at some common scenarios and how to get Bitrise to behave as expected.

Scenario 1: Change in library-a only
What should happen: Only pipeline-a runs. pipeline-b should remain untouched.

Scenario 2: Changes in both library-a and library-b
What should happen: Both pipeline-a and pipeline-b should run independently. pipeline-a builds and tests the updated library-a, while pipeline-b builds and tests the updated library-b

Scenario 3: Changes in a shared library (library-shared)
What should happen: Both pipeline-a and pipeline-b should run independently. Since both depend on the shared library, both need to be rebuilt and tested.

So, how do we solve this? The key is to give each library its own, independent trigger, based on changes within its own directory (and the shared library if applicable). This way, Bitrise can correctly identify which pipelines need to run, regardless of the order of changes. Here’s the YAML config in a covers-all-the-scenarios kind of a setup.

1pipelines:
2  pipeline-a:
3    triggers:
4      push:
5      - branch: develop
6        changed_files: path/to/library-a/*
7
8      - branch: develop
9        changed_files: path/to/library-shared/*
10  
11  pipeline-b:
12    triggers:
13      push:
14      - branch: develop
15        changed_files: path/to/library-b/*
16      - branch: develop
17        changed_files: path/to/library-shared/*

Why one giant pipeline just won't cut it for your monorepo

Imagine you're juggling two balls – easy, right? Now imagine juggling twenty, or even fifty. That's what managing a large monorepo with a single, massive CI/CD pipeline feels like. Combining pipelines might seem efficient at first, but it can quickly become unwieldy, inefficient, and prone to collapse. For mature companies with dozens (or even hundreds) of libraries, a single, monolithic CI/CD pipeline isn’t ideal.

Here's why a "one-pipeline-fits-all" approach falls apart in a large monorepo:

  • Complexity explodes: Your pipeline becomes a monster. Conditional logic to handle dozens of libraries turns your bitrise.yml into an unreadable labyrinth. Maintenance becomes an unpleasant chore.
  • Flexibility vanishes: Need to tweak the build process for one specific library? Good luck navigating that behemoth of a pipeline. Optimizing individual workflows becomes tricky.
  • Build times balloon: A single, monolithic pipeline means longer waits. Even if only a few libraries have changed, the entire pipeline might have to run, slowing everyone down.‍
  • Debugging becomes a nightmare: Tracking down the root cause of an issue in a massive, interconnected pipeline is like finding a needle in a haystack. Debugging becomes a slow, painful process.

In short, while combining pipelines might seem like a shortcut initially, it creates a bottleneck that hinders scalability, slows down development, and increases the risk of failures. For large monorepos, a more modular and independent approach is essential for long-term success. In the next section, we'll explore how to structure your Bitrise workflows for maximum efficiency and maintainability.

What does good look like for using target-based triggers?

1. Define clear and specific trigger conditions:

  • Be precise: Avoid overly broad triggers that fire unnecessarily. Specify branches, tags, and file changes as accurately as possible. Don't trigger a full build if only documentation has changed.
  • Use wildcards and regex effectively: Wildcards (*) are great for simple pattern matching (e.g., feature/*). Regular expressions (regex:) offer more powerful pattern matching for complex scenarios (e.g., semantic versioning for tags).
  • Combine conditions: Use multiple conditions (e.g., branch and changed files) to create very targeted triggers. All conditions must be met for the trigger to fire.
  • Use changed_files strategically: This is your best friend for monorepos or projects with distinct modules. Trigger builds only when relevant files are modified.

Example 1: Feature branch builds with file filtering

1workflows:
2  feature-build:
3    triggers:
4      push:
5      - branch: 'feature/*' # Wildcard for any feature branch
6        changed_files:'src/components/*' # Only if changes in component directory

Why it's good: This trigger targets any branch starting with "feature/" but only triggers if changes are made within the src/components directory . This is efficient because it avoids building for changes in documentation or other unrelated parts of the repository.

Example 2: Pull request validation with specific label

1workflows:
2  pr-validation:
3    triggers:
4      pull_request:
5      - target_branch: main
6        label: "ready-for-review"

Why it's good: This trigger only runs the pr-validation workflow when a pull request is targeted at the main branch and has the "ready-for-review" label. This allows developers to control when the validation process runs, preventing it from running on draft or work-in-progress pull requests.

2. Organize your workflows intuitively

  • Platform-specific workflows: For multi-platform projects (iOS, Android, etc.), create separate workflows for each platform. Use changed_files to trigger the appropriate workflow based on file changes.
  • Modular workflows: Break down complex build processes into smaller, more manageable workflows. This improves readability and makes it easier to debug and maintain.
  • Step bundles: Use Step Bundles for reusing your config blocks in a clever way. For instance, repetitive tasks like cloning your repo or installing dependencies can be wrapped into a Step Bundle and reused effectively.
  • Meaningful names: Give your workflows and triggers descriptive names. This makes it easier to understand their purpose at a glance.

Example 1: Parallel iOS and Android builds on push

1workflows:
2  ios-build:
3    triggers:
4      push:
5      - branch: main  # Same trigger for both workflows
6    # ... iOS build steps ...
7
8  android-build:
9    triggers:
10      push:
11      - branch: main # Same trigger for both workflows
12    # ... Android build steps ...

Why it's good: This setup uses the same push trigger (on the main branch) for both the ios-build and android-build workflows. This ensures that whenever a change is pushed to main, both builds are triggered in parallel. This keeps the iOS and Android versions in sync and streamlines the development process. It's clean, efficient, and easy to understand.

Example 2: Platform-specific feature branch builds

1workflows:
2  ios-feature:
3    triggers:
4      push:
5      - branch: 'feature/*' # Triggered by any feature branch push
6        changed_files: 'ios/**'        # Only if changes in the iOS directory
7    # ... iOS feature build steps ...
8
9  android-feature:
10    triggers:
11      push:
12      - branch: 'feature/*' # Triggered by any feature branch push
13        changed_files: 'android/*'      # Only if changes in the Android directory
14    # ... Android feature build steps ...

Why it's good: This example is more sophisticated. It still uses a shared trigger (feature/* branch), but it uses changed_files to filter which platform-specific workflow is executed. If a change is pushed to a feature branch and the changes are within the ios directory, only the ios-feature workflow runs. The same applies to the android directory. This keeps builds focused and efficient.

Example 3: Monorepo with multiple apps

1workflows:
2  app-a-build:
3    triggers:
4      push:
5      - branch: main
6        changed_files: 'apps/app-a/**' # Only if changes in app-a directory
7    # ... build steps for app-a ...
8
9  app-b-build:
10    triggers:
11      push:
12      - branch: main
13        changed_files: 'apps/app-b/*' # Only if changes in app-b directory
14    # ... build steps for app-b ...

Why it's good: This is a good example for monorepos. Even though the trigger is the same (push to main), the workflows are separated based on which application's code was changed. Only the relevant application's build is triggered. This is efficient and keeps builds focused.

3. Manage triggers effectively

  • enabled property: Use the enabled: false property to temporarily disable triggers without deleting them. This is useful for testing or when you need to pause certain builds.
  • Avoid overly complex logic: Keep your trigger conditions as simple and understandable as possible. Complex logic can make it difficult to troubleshoot issues.
  • Test your triggers: After setting up or modifying triggers, test them thoroughly to ensure they behave as expected. Push changes to a test branch to verify the correct workflows are triggered.

4. Use webhook environment variables for deeper automation

Some juicy, new environment variables that allow using the information in the webhook are now available. They provide detailed information about build triggers, pull request comments, labels, changed files, and commit messages, enabling you to optimize automation, enhance workflow transparency, and streamline resource management.

BITRISE_TRIGGER_BY
  • Description: Identifies the entity that triggered the build.
  • Values:
    • If triggered by a git provider: The git user’s name.
    • If triggered manually from UI: The Bitrise user’s name.
    • For all other triggers (e.g., scheduler, API, custom): The value specified in triggered_by.
  • Availability: Present in all builds (can be empty occasionally).
  • Source: Utilizes the triggered_by build parameter.
  • Supported providers: All git providers.
BITRISE_TRIGGER_METHOD
  • Description: Indicates the method by which the build was triggered.
  • Values:
    • schedule for builds triggered by a scheduler.
    • webhook for builds triggered by a git provider.
    • manual for all other triggers (e.g., rebuild, UI, API).
  • Availability: Present in all builds.
  • Source: Utilizes the triggered_by build parameter.
  • Supported providers: All providers.
BITRISE_GIT_PULL_REQUEST_COMMENT
  • Description: Captures the comment message and its corresponding ID from a pull request.
  • Values: The full comment message.
  • Availability: Available when a pull request comment triggers a build.
  • Supported providers: GitHub, GitLab, Bitbucket.
  • Requirement: Requires a pr_comment trigger map item for builds to be triggered and the environment variables to be populated.
BITRISE_GIT_PULL_REQUEST_COMMENT_ID
  • Description: Captures the comment ID from a pull request.
  • Value: The unique identifier for the comment.
  • Availability: Available when a pull request comment triggers a build.
  • Supported providers: GitHub, GitLab, Bitbucket.
  • Requirement: Requires a pr_comment trigger map item for builds to be triggered and the environment variables to be populated.
BITRISE_GIT_PULL_REQUEST_LABELS
  • Description: Provides a multiline list of new labels added to the pull request along with existing labels.
  • Values: List of labels.
  • Availability: Available when a build is triggered by adding a label to a pull request or any PR event with existing labels.
  • Supported providers: GitHub, GitLab.
  • Requirement: Requires a label trigger map item for new labels to trigger a build. Existing labels do not require a separate trigger map item.
BITRISE_GIT_CHANGED_FILES
  • Description: Lists file paths changed by the pull request.
  • Values: Multiline list of file paths.
  • Availability:
    • When any push event starts the build.
    • When any PR event starts the build and there is a changed_files or commit_message trigger map condition.
  • Supported providers:
    • For push events: GitHub, GitLab.
    • For PR events: GitHub, GitLab, Bitbucket.
  • Requirement:
    • For PR events: Requires a changed_files trigger map item.
    • For push events: No requirement.
BITRISE_GIT_COMMIT_MESSAGES
  • Description: Contains commit messages included by the pull request.
  • Values: Multiline list of commit messages.
  • Availability:
    • When any PR event starts the build.
    • When any push event starts the build.
  • Supported providers:
    • For push events: GitHub, GitLab.
    • For PR events: GitHub, GitLab, Bitbucket.
  • Requirement:
    • For PR events: Requires a commit_message trigger map item.
    • For push events: No requirement.

Cool automation examples you can set up using these new variables

  1. Report back to the PR comment: Use the captured comment ID to post a status update directly to the original comment on the pull request. This can include details such as success, failure, logs, or any other relevant information
  2. Conditional test execution: Run different test suites based on files changed in a pull request. Use BITRISE_GIT_CHANGED_FILES to identify modified parts of the codebase. Based on these changes, trigger specific test suites, such as running frontend tests for UI changes and backend tests for API modifications.
  3. Dynamic documentation updates: Automatically update project documentation when relevant files are changed. Monitor BITRISE_GIT_CHANGED_FILES for documentation-related files. If changes are detected, trigger a build step that regenerates and deploys the updated documentation.
  4. Intelligent deployment decisions: Deploy to different environments based on commit messages or labels. Use BITRISE_GIT_COMMIT_MESSAGES and BITRISE_GIT_PULL_REQUEST_LABELS to determine the deployment environment. For instance, if a commit message includes "[staging]" or a label "ready-for-staging" is present, deploy the code to the staging environment.
  5. Slack notifications for specific events: Send customized Slack notifications when certain actions occur on a pull request. Use BITRISE_GIT_PULL_REQUEST_COMMENT to detect specific keywords in PR comments (e.g., "urgent" or "blocker"). Trigger a Slack notification to alert the team about these critical updates.
  6. Security scans on code changes: Automatically run security scans when sensitive files are modified. Monitor BITRISE_GIT_CHANGED_FILES for changes in security-related files. Trigger a security scan to ensure no vulnerabilities are introduced with the new changes.
  7. Automatic changelog generation: Generate a changelog entry from commit messages in a pull request. Use BITRISE_GIT_COMMIT_MESSAGES to extract commit messages and format them into a changelog entry. This can be automated to update the project's changelog file or release notes.
  8. Pull request quality checks: Enforce PR quality standards by analyzing comments or commit messages. Use BITRISE_GIT_PULL_REQUEST_COMMENT and BITRISE_GIT_COMMIT_MESSAGES to ensure comments and commit messages adhere to predefined quality guidelines. If not, trigger a notification or request changes before merging.

Time to get started with target-based triggers?

By now, you’re no doubt imagining a multitude of ways that you can incorporate target-based triggering into your Bitrise setup to simplify your workflows, enhance control, and increase efficiency. 

Dive into Bitrise's new capabilities, and don't hesitate to share your feedback. 

By following these best practices, you can ensure your CI/CD pipeline is robust and reliable, allowing you to focus more on creating great software and less on managing complex configurations. Together, let's build something amazing!

Not tried Bitrise yet?

It's time for a robust, reliable mobile-first CI/CD solution that allows you the creativity you deserve.

Start for free today

Get Started for free

Start building now, choose a plan later.

Sign Up

Get started for free

Start building now, choose a plan later.