Skip to content
Nx Cloud CI is in private beta 10.6× faster average CI time with 79% less compute. Join the beta

Nx Cloud reads CI configuration from a .nx/pipelines/ci.yaml file in your workspace. The same file format works whether your pipeline runs on Nx Cloud CI or on an external provider such as GitHub Actions.

.nx/pipelines/ci.yaml
worker-templates:
default:
workers: 5 linux-large-js
stop-after:
- build
- test

Start the workers once, early in the main job of your provider's workflow:

Terminal window
npx nx-cloud start-workers
SectionPurpose
worker-templatesNamed worker setups: counts, sizes, and task rules.
launch-templatesMachine setups that workers and jobs run on.
ultracachePaths that Ultracache ignores.
self-healingWhich tasks self-healing CI can fix.
nameDisplay name of the pipeline.
onEvents that start the jobs.
envEnvironment variables for every job and its workers, on Nx Cloud CI.
defaultsDefaults for the steps in every job.
jobsThe jobs and their steps.

External CI doesn't run name, on, env, defaults, and jobs, but still validates them, so one file can serve both kinds of run in the same repository. On external CI, workers get values from the env of their launch template and from start-workers --forward-secrets and --forward-vars.

For editor validation, add the published schema to the first line of the file:

# yaml-language-server: $schema=https://cloud.nx.app/nx-cloud/static/schemas/workflow.schema.json

Named worker setups, at most 50 entries. On external CI, start-workers starts one entry. On Nx Cloud CI, a job picks one with workers: <name>.

An entry is a count string or a map:

.nx/pipelines/ci.yaml
worker-templates:
# A count string
nightly: 10 linux-large-js
# A fixed count with task rules
release:
workers: 2 linux-medium-js; 1 linux-large-js
stop-after: [build]
# Sized by the work in each run
pr:
small: 2 linux-medium-js
medium: 3 linux-medium-js; 1 linux-large-js
large: 8 linux-medium-js; 4 linux-large-js
fail-after: 1

A count string lists <count> <launch-template> pairs separated by ;, with at most 20 templates. The template names can come from your launch-templates or from the pre-built templates.

External CI has no job to take a setup from, so every count needs a template name. A bare count such as 3 only works on Nx Cloud CI, where it reuses the machine setup of the job.

A fixed count of workers, all started with the run. Can't be combined with small, medium, and large.

Worker counts sized by the work the run has to do. All three are required.

  • small starts with the run, every time.
  • At each distributed command, Nx Cloud compares the remaining work, after cache hits, with the stored timings of your workspace and picks a size. With no stored timings yet, as in a new workspace, every command picks large.
  • Counts are totals per template, so picking a larger size adds the difference. A template's count can't go down from one size to the next, and workers are never removed.

By default, small covers up to 33% of the stored task time, medium up to 66%, and large the rest. small and medium can set their own upper limit, a whole percent from 1% to 99%. small's limit must be below medium's, and a size without workload keeps its default:

.nx/pipelines/ci.yaml
worker-templates:
pr:
small: { count: 2 linux-medium-js, workload: 10% }
medium: 4 linux-medium-js
large: 6 linux-medium-js

Rules that send matching tasks to specific launch templates. For the rule format, see assignment rules.

.nx/pipelines/ci.yaml
worker-templates:
default:
workers: 3 linux-medium-js; 1 linux-large-js
assignment-rules:
- targets: [e2e-ci*]
run-on:
- agent: linux-large-js
parallelism: 1

Tasks that must never run on the same worker at the same time, for example because they share a port or a database. Each entry selects tasks by projects, targets, or configurations globs, and cannot-run-with lists the tasks they can't overlap with on one worker.

.nx/pipelines/ci.yaml
worker-templates:
default:
workers: 3 linux-large-js
scheduling-constraints:
# e2e tasks share a resource, so never run two of them on one worker at once
- targets: ['e2e*']
cannot-run-with:
- targets: ['e2e*']

Tasks outside the constraint still share the worker with them. To give a task a worker to itself, set parallelism: false on its target in project configuration.

Targets after which workers shut down: once a command running each listed target has started, workers stop when they have no tasks left. Include at least one target from each Nx command your pipeline runs.

Type: array of strings.

Number of task failures after which the run is marked failed. 0 means never fail, so the run always finishes. 1 stops on the first failure.

Type: integer, minimum 0. Default: 1.

Runs distributed tasks on a background worker on the machine of the main job. On Nx Cloud CI, it's on by default and uses the CPU and memory it detects on the machine. Set cpu and memory to limit what it uses, or set main-job-worker: false to run tasks only on the other workers. External CI doesn't start this worker from ci.yaml. To send tasks to it, use agent: main in assignment-rules. The name main is reserved, so a launch template can't use it.

.nx/pipelines/ci.yaml
worker-templates:
default:
workers: 4 linux-large-js
main-job-worker:
cpu: 3
memory: 6GB

Machine setups, keyed by the name that count strings and runs-on refer to. For the template format, see launch templates.

.nx/pipelines/ci.yaml
launch-templates:
linux-custom:
resource-class: docker_linux_amd64/medium
image: ubuntu22.04-node24.14-v1
init-steps:
- name: Checkout
uses: nrwl/nx-cloud-workflows/v6/workflow-steps/checkout/main.yaml
- name: Install node modules
uses: nrwl/nx-cloud-workflows/v6/workflow-steps/install-node-modules/main.yaml

Nx Cloud looks up a template name in ci.yaml and the files it imports, then in .nx/workflows, then in the public nrwl/nx-cloud-workflows templates.

Paths that Ultracache ignores when it records what a task reads and writes.

.nx/pipelines/ci.yaml
ultracache:
ignore-reads: ['dist/target/**']
ignore-writes: ['dist/target/**']
task-exclusions:
- project: app-e2e
ignore-reads: ['apps/app/build/**']

Entries in task-exclusions select tasks by project, target, and configuration. To exclude paths for a single target instead, see the ultracache target property.

Overrides for self-healing CI. On external CI, these apply to runs started with nx-cloud start-workers.

.nx/pipelines/ci.yaml
self-healing:
fix-tasks-patterns: ['*lint*', '*test*', '!*e2e*']
auto-apply-patterns: ['lint*']

Glob patterns of task IDs that self-healing CI is allowed to attempt fixes for.

Glob patterns of task IDs whose fixes are applied automatically without explicit approval.

launch-templates, ultracache, and worker-templates can each be a path to another file.

.nx/pipelines/ci.yaml
launch-templates: .nx/pipelines/launch-templates.yaml
ultracache: .nx/pipelines/ultracache.yaml
worker-templates: .nx/pipelines/workers.yaml

Paths are relative to the repository root and read at the commit of the run. The imported file holds only the content of the section, with no top-level key naming it, and can't import again. A path must end in .yaml or .yml, can't contain .., and can only use letters, digits, ., _, -, and /. A launch-templates file can also keep a top-level launch-templates: key, so an existing .nx/workflows/agents.yaml can be imported as it is.

Named triggers that start the jobs. Each trigger needs an event of PR or PUSH.

.nx/pipelines/ci.yaml
on:
pull-requests:
event: PR
branches: [main]
paths-ignore: ['docs/**']
main-pushes:
event: PUSH
branches: [main]

Triggers can also filter with branches-ignore and paths, PUSH triggers with tags and tags-ignore, and PR triggers with types. A positive filter can't be combined with its -ignore counterpart.

The jobs of the pipeline, each on its own machine. To convert a GitHub Actions workflow, see move a GitHub Actions workflow to Nx Cloud CI.

KeyDescription
runs-onLaunch template the job runs on.
resource-classMachine size. Use with image and init-steps instead of runs-on.
imageContainer image. Use with resource-class and init-steps instead of runs-on.
init-stepsSteps run once when the machine starts, such as checkout and install.
stepsSteps the job runs in order.
workersWorkers that run the tasks of the job. false turns distribution off.
needsJob or jobs that must finish before this job starts.
ifCondition that must hold for the job to run. See expressions.
envEnvironment variables for every step of the job. Takes expressions.
defaultsDefaults for the steps of the job, such as run.working-directory.
timeout-minutesMinutes before the job is stopped, at most 60.
permissionsPermissions of the repository token. See permissions.
nameDisplay name of the job.
Written asMeans
workers: 3Three workers with the machine setup of the job, started with the job.
workers: 10 linux-large-jsA fixed count of a launch template, started with the job.
workers: { small: 3, medium: 6, large: 8 }Sized by the work.
workers: <name>An entry from worker-templates.
No workersOnly the main-job worker.
workers: falseNo distribution. Each Nx command runs its tasks on the job's machine.

Unless workers is false, the job also distributes tasks to its own main-job worker. Workers get the env of the file and the job.

The main-job worker starts after the init steps and before the steps:

  • Init steps must install Node.js and the workspace dependencies. Otherwise, or with an Nx version too old to distribute, the job fails with a message.
  • It keeps the environment it starts with. Set task variables in the env of the file, job, or launch template, not of a step.
  • It sees files that steps write to the checkout.

Set workers: false on jobs that don't run Nx tasks, such as a deploy job.

A step runs a shell command with run, or a step template with uses and with. It can also set name, id, if, env, shell, working-directory, continue-on-error, and timeout-minutes.

.nx/pipelines/ci.yaml
jobs:
main:
runs-on: linux-medium-js
steps:
- name: Validate dependencies
if: nxcloud.event_name == 'pull_request'
run: node ./tools/validate-deps.mjs
- name: Affected tasks
run: npx nx affected -t lint test build

A job's repository token is read-only by default. Set repository-contents: write to let the job push commits and tags:

.nx/pipelines/release.yaml
on:
main-pushes:
event: PUSH
branches: [main]
jobs:
release:
runs-on: linux-medium-js
permissions:
repository-contents: write
steps:
- run: npx nx release --skip-publish

Put a job that sets this in a file whose only trigger is a push to a protected branch, like the one above. An if in a file that also runs on pull requests doesn't protect it, because the job gets the write token when it starts, before if is checked. See keep secrets safe.

  • Only the job's own machine gets the write token. Its workers keep a read-only one.
  • The token is ${{ secrets.NX_CLOUD_VCS_TOKEN }} (or ${{ secrets.GITHUB_TOKEN }}), and the checkout uses it, so git push works without extra setup.
  • It needs the Nx Cloud GitHub App. In a workspace connected another way, the job fails with a configuration error.
  • Runs for pull requests from forks never get it. A job that sets it fails on a fork.

Nx Cloud CI evaluates ${{ <expression> }} in these keys:

  • env at the top of the file, on a job, and on a step.
  • A step's name, run, and with.
  • if on a job or a step, where the ${{ }} around the expression is optional.

Launch templates also evaluate ${{ }} in their env. Other keys, such as runs-on and workers, take their value as written.

ContextHolds
nxcloudevent_name, sha, ref, ref_name, repository, repository_owner, workspace, server_url, api_url, and is_fork. github reads the same values.
envThe env of the file and the job.
jobstatus, which is success or failure.
steps<id>.outcome, <id>.conclusion, and <id>.outputs of each finished step.
needs<job>.result of each job in needs.
runneros, arch, temp, tool_cache, and name.
secretsStored variables marked secret, and GITHUB_TOKEN, a repository token from the GitHub App. NX_CLOUD_VCS_TOKEN is the same token. See keep secrets safe.
varsThe other stored variables.
inputsThe inputs of a step template, inside that template.

The functions are contains, startsWith, endsWith, format, join, toJSON, fromJSON, hashFiles, and the status functions success(), failure(), and always(). cancelled() isn't supported. An if without a status function runs only when the earlier steps succeeded, as if it started with success() &&.

OperatorMeaning
( )Grouping
. and [ ]Property and index access, such as steps.build.outputs['version']
.*Every element, such as needs.*.result
!Not
==, !=, <, <=, >, >=Comparison

&& (and) and || (or) return one of their operands, so ${{ vars.NPM_REGISTRY || 'https://registry.npmjs.org' }} gives a default.

Literals are null, true, false, numbers, and strings in single quotes. Write '' for a quote inside a string.

  • Strings compare without regard to case.
  • Values of different types are converted to numbers before they're compared.
  • false, 0, '', null, and NaN are false. Every other value is true.
.nx/pipelines/ci.yaml
env:
NPM_REGISTRY: ${{ vars.NPM_REGISTRY || 'https://registry.npmjs.org' }}
CACHE_KEY: ${{ format('deps-{0}-{1}', runner.os, hashFiles('pnpm-lock.yaml')) }}
jobs:
main:
runs-on: linux-medium-js
steps:
- name: Read version
id: version
run: echo "value=$(node -p 'require(\"./package.json\").version')" >> "$NX_CLOUD_OUTPUT"
- name: Lint
id: lint
continue-on-error: true
run: npx nx affected -t lint
# Only on pushes to main
- name: Publish
if: nxcloud.event_name == 'push' && nxcloud.ref_name == 'main'
run: npx nx release publish --version ${{ steps.version.outputs.value }}
# A leading ! needs ${{ }} or quotes, because YAML reads a bare ! as a tag
- name: Upload coverage
if: ${{ !nxcloud.is_fork }}
run: npx codecov
# Runs although Lint's failure was allowed
- name: Report lint failures
if: steps.lint.outcome == 'failure'
run: node ./tools/report-lint.mjs
# Runs only when an earlier step failed
- name: Collect logs
if: failure()
run: tar czf logs.tgz dist/logs
- name: Long-lived branches
if: contains(fromJSON('["main", "next"]'), nxcloud.ref_name)
run: npx nx affected -t e2e
deploy:
needs: main
if: needs.main.result == 'success' && nxcloud.event_name == 'push'
runs-on: linux-medium-js
steps:
- name: Deploy
run: ./deploy.sh

With continue-on-error, a failed step has outcome set to failure and conclusion set to success. On a pull request, nxcloud.ref is refs/pull/<number>/merge, so branch checks on ref or ref_name only make sense on push.

Files written for .nx/workflows may use {{ secrets.NAME }} or {{ nxBranch }}. These still work, but use ${{ }} in new files.

Every step runs with these variables:

VariableValueGitHub Actions name
NX_CLOUD_CItrue, so scripts can tell they're running on Nx Cloud CINone, see below
NX_CLOUD_SHACommit being builtGITHUB_SHA
NX_CLOUD_REFFull ref, such as refs/heads/main or refs/pull/12/mergeGITHUB_REF
NX_CLOUD_REF_NAMEShort ref, such as mainGITHUB_REF_NAME
NX_CLOUD_EVENT_NAMEpush or pull_requestGITHUB_EVENT_NAME
NX_CLOUD_REPOSITORYowner/repoGITHUB_REPOSITORY
NX_CLOUD_REPOSITORY_OWNERownerGITHUB_REPOSITORY_OWNER
NX_CLOUD_REPOSITORY_DIRDirectory the repository is checked out toGITHUB_WORKSPACE
NX_CLOUD_VCS_SERVER_URLGit host, such as https://github.comGITHUB_SERVER_URL
NX_CLOUD_VCS_API_URLGit host API, such as https://api.github.comGITHUB_API_URL
NX_CLOUD_RUNNER_OSOperating system of the machineRUNNER_OS
NX_CLOUD_RUNNER_ARCHCPU architecture of the machineRUNNER_ARCH
NX_CLOUD_RUNNER_TEMPTemporary directoryRUNNER_TEMP
NX_CLOUD_RUNNER_TOOL_CACHEDirectory for cached toolsRUNNER_TOOL_CACHE
NX_CLOUD_RUNNER_NAMEName of the machineRUNNER_NAME

The same values are in the nxcloud and runner expression contexts. GITHUB_ACTIONS and GITHUB_RUN_ID aren't set, so tools don't detect the job as running in GitHub Actions. Check NX_CLOUD_CI instead.

A step can write to these files to affect later steps:

FileWriteEffectGitHub Actions name
$NX_CLOUD_ENVNAME=valueSets NAME for the following steps of the job$GITHUB_ENV
$NX_CLOUD_PATHA directory per lineAdds it to the front of PATH for the following steps$GITHUB_PATH
$NX_CLOUD_OUTPUTname=valueSets steps.<id>.outputs.<name>$GITHUB_OUTPUT
$NX_CLOUD_STEP_SUMMARYTextSaved as the step's summary$GITHUB_STEP_SUMMARY
$NX_CLOUD_STATEname=valueState a step template reads back later$GITHUB_STATE
.nx/pipelines/ci.yaml
steps:
- name: Read version
id: version
run: echo "value=$(node -p 'require(\"./package.json\").version')" >> "$NX_CLOUD_OUTPUT"
- name: Add tools to PATH
run: echo "$HOME/.cargo/bin" >> "$NX_CLOUD_PATH"
- name: Build
run: npx nx run app:build --version=${{ steps.version.outputs.value }}

$NX_CLOUD_ENV takes one NAME=value per line. For a multi-line value, write to $GITHUB_ENV with NAME<<EOF.

  • Pull requests from forks get none of the variables from the Environment variables page and a read-only repository token. Self-healing CI doesn't run on them, and a job that sets repository-contents: write fails. If the author isn't an owner, member, collaborator, or earlier contributor of the repository, a member of your Nx Cloud workspace approves the run before it starts.
  • Pull requests from branches in your repository get your secrets and run the code from that branch. Anyone who can push a branch can read them.
  • Secrets that can publish or deploy, and repository-contents: write, belong in a pipeline file whose only trigger is a push to a protected branch.
  • if doesn't protect anything. A pull request runs its own copy of ci.yaml, so it can change any condition. Use if: ${{ !nxcloud.is_fork }} only to skip steps that would fail without secrets:
- name: Upload coverage
if: ${{ !nxcloud.is_fork }}
run: npx codecov --token "$CODECOV_TOKEN"
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

On external CI, start-workers --forward-secrets forwards whatever the main job has, fork runs included, so your CI provider's settings decide whether forks get secrets.

Nx Cloud CI doesn't support these yet:

  • Events other than push and pull request, including schedule, workflow_dispatch, and merge queues.
  • strategy.matrix, concurrency, services, and job outputs.
  • Actions from the GitHub Marketplace and action.yml files, including actions/cache and actions/upload-artifact. uses runs Nx Cloud step templates.
  • OIDC identity tokens for cloud providers. Use secrets for now.
  • macOS and Windows. Jobs run on Linux.
  • Git LFS. The job image doesn't include it, so a repository that uses LFS checks out pointer files.

A job runs for at most 60 minutes, and that is also the default for timeout-minutes. You can't turn off the Nx Cloud: <name> status while Nx Cloud CI is on. Checks for each job, named <name> / <job>, are off by default. Turn on Show check runs for individual Nx Cloud CI jobs in the VCS integration settings of your workspace. A configuration error in a pipeline file shows as a failed <file> / configuration check.