Using Nx Agents is the easiest way to distribute task execution, but your organization may not be able to use hosted Nx Agents. You can set up distributed task execution on your own CI provider using the recipes below.
Our reusable GitHub workflow represents a good set of defaults that works for a large number of our users. However, reusable GitHub workflows come with their limitations.
If the reusable workflow above doesn't satisfy your needs you should create a custom workflow. If you were to rewrite the reusable workflow yourself, it would look something like this:
name: CIon:  push:    branches:      - main  pull_request:
# Needed for nx-set-shas when run on the main branchpermissions:  actions: read  contents: read
env:  NX_CLOUD_DISTRIBUTED_EXECUTION: true # this enables DTE  NX_BRANCH: ${{ github.event.number || github.ref_name }}  NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}  NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # this is needed if our pipeline publishes to npm
jobs:  main:    name: Nx Cloud - Main Job    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4        name: Checkout [Pull Request]        if: ${{ github.event_name == 'pull_request' }}        with:          # By default, PRs will be checked-out based on the Merge Commit, but we want the actual branch HEAD.          ref: ${{ github.event.pull_request.head.sha }}          # We need to fetch all branches and commits so that Nx affected has a base to compare against.          fetch-depth: 0          filter: tree:0
      - uses: actions/checkout@v4        name: Checkout [Default Branch]        if: ${{ github.event_name != 'pull_request' }}        with:          # We need to fetch all branches and commits so that Nx affected has a base to compare against.          fetch-depth: 0          filter: tree:0
      # Set node/npm/yarn versions using volta      - uses: volta-cli/action@v4        with:          package-json-path: '${{ github.workspace }}/package.json'
      - name: Use the package manager cache if available        uses: actions/setup-node@v3        with:          node-version: 20          cache: 'npm'
      - name: Install dependencies        run: npm ci
      - name: Check out the default branch        run: git branch --track main origin/main
      - name: Initialize the Nx Cloud distributed CI run and stop agents when the build tasks are done        run: npx nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after=e2e-ci
      - name: Check the formatting        run: npx nx-cloud record -- nx format:check
      - name: Lint, test, build, and run e2e        run: npx nx affected -t lint,test,build,e2e-ci --configuration=ci
  agents:    name: Agent ${{ matrix.agent }}    runs-on: ubuntu-latest    strategy:      matrix:        # Add more agents here as your repository expands        agent: [1, 2, 3]    steps:      - name: Checkout        uses: actions/checkout@v4
      # Set node/npm/yarn versions using volta      - uses: volta-cli/action@v4        with:          package-json-path: '${{ github.workspace }}/package.json'
      - name: Use the package manager cache if available        uses: actions/setup-node@v3        with:          node-version: 20          cache: 'npm'
      - name: Install dependencies        run: npm ci
      - name: Start Nx Agent ${{ matrix.agent }}        run: npx nx-cloud start-agent        env:          NX_AGENT_NAME: ${{ matrix.agent }}There are comments throughout the workflow to help you understand what is happening in each section.
Run agents directly on Circle CI with the workflow below:
version: 2.1orbs:  nx: nrwl/nx@1.5.1jobs:  main:    docker:      - image: cimg/node:lts-browsers    steps:      - checkout      - run: npm ci      - nx/set-shas
      # Tell Nx Cloud to use DTE and stop agents when the e2e-ci tasks are done      - run: npx nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after=e2e-ci      # Send logs to Nx Cloud for any CLI command      - run: npx nx-cloud record -- nx format:check      # Lint, test, build and run e2e on agent jobs for everything affected by a change      - run: npx nx affected --base=$NX_BASE --head=$NX_HEAD -t lint,test,build,e2e-ci --parallel=2 --configuration=ci  agent:    docker:      - image: cimg/node:lts-browsers    parameters:      ordinal:        type: integer    steps:      - checkout      - run: npm ci      # Wait for instructions from Nx Cloud      - run:          command: npx nx-cloud start-agent          no_output_timeout: 60mworkflows:  build:    jobs:      - agent:          matrix:            parameters:              ordinal: [1, 2, 3]      - mainThis configuration is setting up two types of jobs - a main job and three agent jobs.
The main job tells Nx Cloud to use DTE and then runs normal Nx commands as if this were a single pipeline set up. Once the commands are done, it notifies Nx Cloud to stop the agent jobs.
The agent jobs set up the repo and then wait for Nx Cloud to assign them tasks.
Run agents directly on Azure Pipelines with the workflow below:
trigger:  - mainpr:  - main
variables:  CI: 'true'  ${{ if eq(variables['Build.Reason'], 'PullRequest') }}:    NX_BRANCH: $(System.PullRequest.PullRequestNumber)    TARGET_BRANCH: $[replace(variables['System.PullRequest.TargetBranch'],'refs/heads/','origin/')]    BASE_SHA: $(git merge-base $(TARGET_BRANCH) HEAD)  ${{ if ne(variables['Build.Reason'], 'PullRequest') }}:    NX_BRANCH: $(Build.SourceBranchName)    BASE_SHA: $(git rev-parse HEAD~1)  HEAD_SHA: $(git rev-parse HEAD)
jobs:  - job: agents    strategy:      parallel: 3    displayName: Nx Cloud Agent    pool:      vmImage: 'ubuntu-latest'    steps:      - checkout: self        fetchDepth: 0        fetchFilter: tree:0        persistCredentials: true
      - script: npm ci      - script: npx nx-cloud start-agent
  - job: main    displayName: Nx Cloud Main    pool:      vmImage: 'ubuntu-latest'    steps:      # Get last successfull commit from Azure Devops CLI      - bash: |          LAST_SHA=$(az pipelines build list --branch $(Build.SourceBranchName) --definition-ids $(System.DefinitionId) --result succeeded --top 1 --query "[0].triggerInfo.\"ci.sourceSha\"")          if [ -z "$LAST_SHA" ]          then            echo "Last successful commit not found. Using fallback 'HEAD~1': $BASE_SHA"          else            echo "Last successful commit SHA: $LAST_SHA"            echo "##vso[task.setvariable variable=BASE_SHA]$LAST_SHA"          fi        displayName: 'Get last successful commit SHA'        condition: ne(variables['Build.Reason'], 'PullRequest')        env:          AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
      - script: git branch --track main origin/main      - script: npm ci      - script: npx nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after="e2e-ci"      - script: npx nx-cloud record -- nx format:check --base=$(BASE_SHA) --head=$(HEAD_SHA)      - script: npx nx affected --base=$(BASE_SHA) --head=$(HEAD_SHA) -t lint,test,build,e2e-ci --parallel=2 --configuration=ciThis configuration is setting up two types of jobs - a main job and three agent jobs.
The main job tells Nx Cloud to use DTE and then runs normal Nx commands as if this were a single pipeline set up. Once the commands are done, it notifies Nx Cloud to stop the agent jobs.
The agent jobs set up the repo and then wait for Nx Cloud to assign them tasks.
Run agents directly on Bitbucket Pipelines with the workflow below:
image: node:20
clone:  depth: full
definitions:  steps:    - step: &agent        name: Agent        script:          - export NX_BRANCH=$BITBUCKET_PR_ID
          - npm ci          - npx nx-cloud start-agent
pipelines:  pull-requests:    '**':      - parallel:          - step:              name: CI              script:                - export NX_BRANCH=$BITBUCKET_PR_ID
                - npm ci                - npx nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after="e2e-ci"                - npx nx-cloud record -- nx format:check                - npx nx affected --target=lint,test,build,e2e-ci --parallel=2          - step: *agent          - step: *agent          - step: *agentThis configuration is setting up two types of jobs - a main job and three agent jobs.
The main job tells Nx Cloud to use DTE and then runs normal Nx commands as if this were a single pipeline set up. Once the commands are done, it notifies Nx Cloud to stop the agent jobs.
The agent jobs set up the repo and then wait for Nx Cloud to assign them tasks.
Run agents directly on GitLab with the workflow below:
image: node:18
# Creating template for DTE agents.dte-agent:  interruptible: true  cache:    key:      files:        - yarn.lock    paths:      - '.yarn-cache/'  script:    - yarn install --cache-folder .yarn-cache --prefer-offline --frozen-lockfile    - yarn nx-cloud start-agent
# Creating template for a job running DTE (orchestrator).base-pipeline:  interruptible: true  only:    - main    - merge_requests  cache:    key:      files:        - yarn.lock    paths:      - '.yarn-cache/'  before_script:    - yarn install --cache-folder .yarn-cache --prefer-offline --frozen-lockfile    - NX_HEAD=$CI_COMMIT_SHA    - NX_BASE=${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_BEFORE_SHA}
  artifacts:    expire_in: 5 days    paths:      - dist
# Main job running DTEnx-dte:  stage: affected  extends: .base-pipeline  script:    - yarn nx-cloud start-ci-run --distribute-on="manual" --stop-agents-after=e2e-ci    - yarn nx-cloud record -- nx format:check --base=$NX_BASE --head=$NX_HEAD    - yarn nx affected --base=$NX_BASE --head=$NX_HEAD -t lint,test,build,e2e-ci --parallel=2
# Create as many agents as you wantnx-dte-agent1:  extends: .dte-agent  stage: affectednx-dte-agent2:  extends: .dte-agent  stage: affectednx-dte-agent3:  extends: .dte-agent  stage: affectedThis configuration is setting up two types of jobs - a main job and three agent jobs.
The main job tells Nx Cloud to use DTE and then runs normal Nx commands as if this were a single pipeline set up. Once the commands are done, it notifies Nx Cloud to stop the agent jobs.
The agent jobs set up the repo and then wait for Nx Cloud to assign them tasks.
Run agents directly on Jenkins with the workflow below:
// Jenkinsfilepipeline {    agent none    environment {        NX_BRANCH = env.BRANCH_NAME.replace('PR-', '')    }    stages {        stage('Pipeline') {            parallel {                stage('Main') {                    when {                        branch 'main'                    }                    agent any                    steps {                        sh "npm ci"                        sh "npx nx-cloud start-ci-run --distribute-on='manual' --stop-agents-after='e2e-ci'"                        sh "npx nx-cloud record -- nx format:check"                        sh "npx nx affected --base=HEAD~1 -t lint,test,build,e2e-ci --configuration=ci --parallel=2"                    }                }                stage('PR') {                    when {                        not { branch 'main' }                    }                    agent any                    steps {                        sh "npm ci"                        sh "npx nx-cloud start-ci-run --distribute-on='manual' --stop-agents-after='e2e-ci'"                        sh "npx nx-cloud record -- nx format:check"                        sh "npx nx affected --base origin/${env.CHANGE_TARGET} -t lint,test,build,e2e-ci --parallel=2 --configuration=ci"                    }                }
                # Add as many agent you want                stage('Agent1') {                   agent any                   steps {                    sh "npm ci"                    sh "npx nx-cloud start-agent"                   }                }                stage('Agent2') {                   agent any                   steps {                    sh "npm ci"                    sh "npx nx-cloud start-agent"                   }                }                stage('Agent3') {                   agent any                   steps {                    sh "npm ci"                    sh "npx nx-cloud start-agent"                   }                }            }        }    }}This configuration is setting up two types of jobs - a main job and three agent jobs.
The main job tells Nx Cloud to use DTE and then runs normal Nx commands as if this were a single pipeline set up. Once the commands are done, it notifies Nx Cloud to stop the agent jobs.
The agent jobs set up the repo and then wait for Nx Cloud to assign them tasks.
Rerunning jobs with DTE
Section titled “Rerunning jobs with DTE”Rerunning only failed jobs results in agent jobs not running, which causes the CI pipeline to hang and eventually timeout. This is a common pitfall when using a CI providers "rerun failed jobs", or equivalent, feature since agent jobs will always complete successfully.
To enforce rerunning all jobs, you can set up your CI pipeline to exit early with a helpful error. For example:
You reran only failed jobs, but CI requires rerunning all jobs. Rerun all jobs in the pipeline to prevent this error.
At a high level:
- Create a job that always succeeds and uploads an artifact on the pipeline with the run attempt number of the pipeline.
- The main and agent jobs can read the artifact file when starting and assert they are on the same re-try attempt.
- If the reattempt number does not match, then error with a message stating to rerun all jobs. Otherwise, the pipelines are on the same rerun and can proceed as normally.