> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Targeting runner groups from CI

> How a CI job picks which Aspect Workflows runner group it lands on, for GitHub Actions, Buildkite, GitLab CI and CircleCI, and how to split work across groups with different hardware.

export const gatedAccess = (user, group) => {
  const loggedIn = !!(user && user.loggedIn);
  const groups = user && user.tenantMetadata && user.tenantMetadata.docsGroups || [];
  if (loggedIn && (!group || groups.indexOf(group) >= 0)) {
    return "entitled";
  }
  return loggedIn ? "signed-in" : "anonymous";
};

export const GatedLink = ({access, href, group, children}) => {
  const note = group ? "Aspect Enterprise customers" : "free Aspect account";
  const muted = {
    fontSize: "0.85em",
    opacity: 0.7,
    whiteSpace: "nowrap"
  };
  if (access === "entitled") {
    return <a href={href}>{children}</a>;
  }
  if (access !== "signed-in") {
    return <span>
        <a href={"/login?redirect=" + encodeURIComponent(href)}>{children}</a>
        <span style={muted}> (sign in: {note})</span>
      </span>;
  }
  return <span>
      {children}
      <span style={muted}> ({note})</span>
    </span>;
};

A **runner group** is a set of identically configured machines: one instance type, one scaling range, one warming set. An Aspect Enterprise deployment usually has several, and your pipeline decides which one each job lands on using your CI provider's own targeting mechanism. There is no Aspect-specific syntax.

## The mechanism, per provider

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  permissions:
    contents: read
    id-token: write    # artifact uploads and the PR summary comment

  jobs:
    test:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      steps:
        - uses: actions/checkout@v6
        - uses: aspect-build/setup-aspect@ebca96eb49ef58c00d4226de8bd3a0813507dd87 # v2026.38.3
        - run: bazel test //...
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: "Test"
      agents:
        queue: aspect-default
      plugins:
        - aspect-build/setup-aspect#8de9aed254d0699baf201f66e2076fc8c09f42b9: ~ # v2026.38.2
      command: bazel test //...
  ```

  ```yaml GitLab theme={null}
  include:
    - component: $CI_SERVER_FQDN/aspect-build/setup-aspect-gitlab-component/setup@2026.38.3

  test:
    extends: .setup-aspect
    tags: [aspect-workflows, aspect-default]
    script:
      - bazel test //...
  ```

  ```yaml CircleCI theme={null}
  version: 2.1

  orbs:
    setup-aspect: aspect-build/setup-aspect@2026.38.2

  jobs:
    test:
      machine: true
      resource_class: YOUR-ORG/aspect-default
      working_directory: /mnt/ephemeral/workdir
      steps:
        - checkout
        - setup-aspect/setup
        - run: bazel test //...
  ```
</CodeGroup>

| Provider       | Selector          | What it matches on the runner group                                                    |
| -------------- | ----------------- | -------------------------------------------------------------------------------------- |
| GitHub Actions | `runs-on:` labels | The group's runner labels                                                              |
| Buildkite      | `agents: queue:`  | The group's queue                                                                      |
| GitLab         | `tags:`           | `aspect-workflows`, plus the group's queue                                             |
| CircleCI       | `resource_class:` | The CircleCI resource class the group's runners register under, namespaced by your org |

The selector matches those values, not the group's name, though they're often the same. `aspect-default` above is an example. Your Aspect contact gives you the values for your deployment, or they're in your Terraform if you deploy it yourself.

A CircleCI job on a Workflows runner also sets `working_directory: /mnt/ephemeral/workdir`, the directory the runner cleans between jobs.

<Note>
  **GitHub Actions has its own "runner groups" feature**, and it's a different thing. GitHub's
  runner groups control which repositories may use an organization's runners. Aspect Workflows
  runner groups define the machines. They coexist; only the names collide.
</Note>

Each example includes the recommended setup step, which a job needs before it calls `bazel` directly. [The setup step](/docs/aspect-workflows/enterprise/connect/ci-setup#the-setup-step) covers what it does, its inputs, and how to run it without an integration.

## `bazel` and `aspect` both run there

Targeting is independent of what the job runs. With the setup step in place, a group takes vanilla `bazel` and [Aspect CLI tasks](/docs/cli/tasks-ci) equally, and you can mix them across jobs on the same group:

```yaml GitHub Actions theme={null}
permissions:
  contents: read
  id-token: write    # artifact uploads and the PR summary comment

jobs:
  # Vanilla Bazel, unchanged from whatever you run today.
  test-vanilla:
    runs-on: [self-hosted, aspect-workflows, aspect-default]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@ebca96eb49ef58c00d4226de8bd3a0813507dd87 # v2026.38.3
      - run: bazel test //...

  # The same build through the Aspect CLI: adds status checks and PR comments.
  # Those post through the GitHub or GitLab App, so this one needs the token.
  test-aspect:
    runs-on: [self-hosted, aspect-workflows, aspect-default]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@ebca96eb49ef58c00d4226de8bd3a0813507dd87 # v2026.38.3
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect test --task:name test -- //...
```

## Why use more than one

Groups put different work on the hardware it needs, so the cheap common case doesn't pay for the expensive rare one.

```yaml GitHub Actions theme={null}
permissions:
  contents: read
  id-token: write    # artifact uploads and the PR summary comment

jobs:
  # Small, always-warm group: `aspect format` builds the formatter target and
  # runs it over the changed files. It never builds the graph, so it has no use
  # for a large machine.
  format:
    runs-on: [self-hosted, aspect-workflows, aspect-small]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@ebca96eb49ef58c00d4226de8bd3a0813507dd87 # v2026.38.3
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect format --task:name format

  # Large group that scales from zero: nightly full builds.
  nightly:
    runs-on: [self-hosted, aspect-workflows, aspect-large]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@ebca96eb49ef58c00d4226de8bd3a0813507dd87 # v2026.38.3
      - run: bazel build //...

  # GPU instances for model tests.
  gpu-tests:
    runs-on: [self-hosted, aspect-workflows, aspect-gpu]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@ebca96eb49ef58c00d4226de8bd3a0813507dd87 # v2026.38.3
      - run: bazel test //ml/...
```

Common reasons to split:

* **Architecture.** An Arm group for Arm targets, an x86 group for everything else.
* **Memory.** One large-memory group for the link step or the test that needs 128 GiB, so the rest of the fleet doesn't have to be that size.
* **Specialist hardware.** GPUs for model training and inference tests.
* **Latency versus cost.** A small group with a non-zero minimum for PR jobs; larger groups that scale from zero for scheduled work.

## Runner groups and remote execution worker pools are different things

A runner group is where the *CI job* runs: the machine that checks out your repository and invokes `bazel`. A [remote execution worker pool](/docs/aspect-workflows/platform/guides/remote-execution-worker-pools) is where individual *Bazel actions* run once that invocation fans out.

Without remote execution the runner group is doing all the work, and its size is the limit. With it, Bazel on the runner still schedules and dispatches every action, tracks the results and moves the outputs, so size the group to the fan-out you expect, not to the work the workers absorb.

## Adding or changing a group

* **Self-hosted:** the runner groups block in your Terraform. See the <GatedLink access={gatedAccess(user, "workflows-subscriber")} href="/docs/aspect-workflows/enterprise/self-hosted/deploy-on-aws" group="workflows-subscriber">deployment guides</GatedLink>.
* **Hosted by Aspect:** [request it](/docs/aspect-workflows/enterprise/hosted/requesting-changes). Say what the jobs need and Aspect sizes it.

Either way, a new group needs the matching selector in your pipeline before anything lands on it.

## Related

* [CI runners](/docs/aspect-workflows/platform/features/ci-runners): what the runners do and why they stay warm.
* [Warming](/docs/aspect-workflows/enterprise/ci-runners/warming): how a new runner starts fast.
* CI pipelines on Workflows runners: [GitHub Actions](/docs/aspect-workflows/enterprise/connect/ci-pipelines/github-actions), [Buildkite](/docs/aspect-workflows/enterprise/connect/ci-pipelines/buildkite), [GitLab CI](/docs/aspect-workflows/enterprise/connect/ci-pipelines/gitlab) and [CircleCI](/docs/aspect-workflows/enterprise/connect/ci-pipelines/circleci).
