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.
worker-templates: default: workers: 5 linux-large-js stop-after: - build - testStart the workers once, early in the main job of your provider's workflow:
npx nx-cloud start-workerson: pull-requests: event: PR main-pushes: event: PUSH branches: [main]
worker-templates: default: workers: 5 linux-large-js stop-after: - build - test
jobs: main: runs-on: linux-medium-js workers: default steps: - run: npx nx affected -t lint test buildTop-level structure
Section titled “Top-level structure”| Section | Purpose |
|---|---|
worker-templates | Named worker setups: counts, sizes, and task rules. |
launch-templates | Machine setups that workers and jobs run on. |
ultracache | Paths that Ultracache ignores. |
self-healing | Which tasks self-healing CI can fix. |
name | Display name of the pipeline. |
on | Events that start the jobs. |
env | Environment variables for every job and its workers, on Nx Cloud CI. |
defaults | Defaults for the steps in every job. |
jobs | The 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.jsonworker-templates
Section titled “worker-templates”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:
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: 1A 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.
workers
Section titled “workers”A fixed count of workers, all started with the run. Can't be combined with small, medium, and large.
small, medium, and large
Section titled “small, medium, and large”Worker counts sized by the work the run has to do. All three are required.
smallstarts 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:
worker-templates: pr: small: { count: 2 linux-medium-js, workload: 10% } medium: 4 linux-medium-js large: 6 linux-medium-jsassignment-rules
Section titled “assignment-rules”Rules that send matching tasks to specific launch templates. For the rule format, see assignment rules.
worker-templates: default: workers: 3 linux-medium-js; 1 linux-large-js assignment-rules: - targets: [e2e-ci*] run-on: - agent: linux-large-js parallelism: 1scheduling-constraints
Section titled “scheduling-constraints”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.
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.
stop-after
Section titled “stop-after”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.
fail-after
Section titled “fail-after”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.
main-job-worker
Section titled “main-job-worker”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.
worker-templates: default: workers: 4 linux-large-js main-job-worker: cpu: 3 memory: 6GBlaunch-templates
Section titled “launch-templates”Machine setups, keyed by the name that count strings and runs-on refer to. For the template format, see launch templates.
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.yamlNx 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.
ultracache
Section titled “ultracache”Paths that Ultracache ignores when it records what a task reads and writes.
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.
self-healing
Section titled “self-healing”Overrides for self-healing CI. On external CI, these apply to runs started with nx-cloud start-workers.
self-healing: fix-tasks-patterns: ['*lint*', '*test*', '!*e2e*'] auto-apply-patterns: ['lint*']fix-tasks-patterns
Section titled “fix-tasks-patterns”Glob patterns of task IDs that self-healing CI is allowed to attempt fixes for.
auto-apply-patterns
Section titled “auto-apply-patterns”Glob patterns of task IDs whose fixes are applied automatically without explicit approval.
Import a section from another file
Section titled “Import a section from another file”launch-templates, ultracache, and worker-templates can each be a path to another file.
launch-templates: .nx/pipelines/launch-templates.yamlultracache: .nx/pipelines/ultracache.yamlworker-templates: .nx/pipelines/workers.yamlPaths 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.
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.
| Key | Description |
|---|---|
runs-on | Launch template the job runs on. |
resource-class | Machine size. Use with image and init-steps instead of runs-on. |
image | Container image. Use with resource-class and init-steps instead of runs-on. |
init-steps | Steps run once when the machine starts, such as checkout and install. |
steps | Steps the job runs in order. |
workers | Workers that run the tasks of the job. false turns distribution off. |
needs | Job or jobs that must finish before this job starts. |
if | Condition that must hold for the job to run. See expressions. |
env | Environment variables for every step of the job. Takes expressions. |
defaults | Defaults for the steps of the job, such as run.working-directory. |
timeout-minutes | Minutes before the job is stopped, at most 60. |
permissions | Permissions of the repository token. See permissions. |
name | Display name of the job. |
jobs.<job>.workers
Section titled “jobs.<job>.workers”| Written as | Means |
|---|---|
workers: 3 | Three workers with the machine setup of the job, started with the job. |
workers: 10 linux-large-js | A 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 workers | Only the main-job worker. |
workers: false | No 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
envof 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.
jobs.<job>.steps
Section titled “jobs.<job>.steps”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.
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 buildjobs.<job>.permissions
Section titled “jobs.<job>.permissions”A job's repository token is read-only by default. Set repository-contents: write to let the job push commits and tags:
on: main-pushes: event: PUSH branches: [main]
jobs: release: runs-on: linux-medium-js permissions: repository-contents: write steps: - run: npx nx release --skip-publishPut 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, sogit pushworks 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.
Expressions
Section titled “Expressions”Nx Cloud CI evaluates ${{ <expression> }} in these keys:
envat the top of the file, on a job, and on a step.- A step's
name,run, andwith. ifon 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.
| Context | Holds |
|---|---|
nxcloud | event_name, sha, ref, ref_name, repository, repository_owner, workspace, server_url, api_url, and is_fork. github reads the same values. |
env | The env of the file and the job. |
job | status, 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. |
runner | os, arch, temp, tool_cache, and name. |
secrets | Stored 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. |
vars | The other stored variables. |
inputs | The 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() &&.
Operators
Section titled “Operators”| Operator | Meaning |
|---|---|
( ) | 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, andNaNare false. Every other value is true.
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.shWith 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.
{{ }} in older files
Section titled “{{ }} in older files”Files written for .nx/workflows may use {{ secrets.NAME }} or {{ nxBranch }}. These still work, but use ${{ }} in new files.
Variables and files in each step
Section titled “Variables and files in each step”Every step runs with these variables:
| Variable | Value | GitHub Actions name |
|---|---|---|
NX_CLOUD_CI | true, so scripts can tell they're running on Nx Cloud CI | None, see below |
NX_CLOUD_SHA | Commit being built | GITHUB_SHA |
NX_CLOUD_REF | Full ref, such as refs/heads/main or refs/pull/12/merge | GITHUB_REF |
NX_CLOUD_REF_NAME | Short ref, such as main | GITHUB_REF_NAME |
NX_CLOUD_EVENT_NAME | push or pull_request | GITHUB_EVENT_NAME |
NX_CLOUD_REPOSITORY | owner/repo | GITHUB_REPOSITORY |
NX_CLOUD_REPOSITORY_OWNER | owner | GITHUB_REPOSITORY_OWNER |
NX_CLOUD_REPOSITORY_DIR | Directory the repository is checked out to | GITHUB_WORKSPACE |
NX_CLOUD_VCS_SERVER_URL | Git host, such as https://github.com | GITHUB_SERVER_URL |
NX_CLOUD_VCS_API_URL | Git host API, such as https://api.github.com | GITHUB_API_URL |
NX_CLOUD_RUNNER_OS | Operating system of the machine | RUNNER_OS |
NX_CLOUD_RUNNER_ARCH | CPU architecture of the machine | RUNNER_ARCH |
NX_CLOUD_RUNNER_TEMP | Temporary directory | RUNNER_TEMP |
NX_CLOUD_RUNNER_TOOL_CACHE | Directory for cached tools | RUNNER_TOOL_CACHE |
NX_CLOUD_RUNNER_NAME | Name of the machine | RUNNER_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:
| File | Write | Effect | GitHub Actions name |
|---|---|---|---|
$NX_CLOUD_ENV | NAME=value | Sets NAME for the following steps of the job | $GITHUB_ENV |
$NX_CLOUD_PATH | A directory per line | Adds it to the front of PATH for the following steps | $GITHUB_PATH |
$NX_CLOUD_OUTPUT | name=value | Sets steps.<id>.outputs.<name> | $GITHUB_OUTPUT |
$NX_CLOUD_STEP_SUMMARY | Text | Saved as the step's summary | $GITHUB_STEP_SUMMARY |
$NX_CLOUD_STATE | name=value | State a step template reads back later | $GITHUB_STATE |
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.
Keep secrets safe
Section titled “Keep secrets safe”- 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: writefails. 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. ifdoesn't protect anything. A pull request runs its own copy ofci.yaml, so it can change any condition. Useif: ${{ !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 limits
Section titled “Nx Cloud CI limits”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 joboutputs.- Actions from the GitHub Marketplace and
action.ymlfiles, includingactions/cacheandactions/upload-artifact.usesruns 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.