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

# CI / GitHub Actions

> Run tests in pre-provisioned RemoteMux environments from GitHub Actions and other CI runners.

RemoteMux environments are durable cloud VMs. CI runners can create ephemeral
workspaces inside them to run tests, then clean up. The environment stays
warm between runs, so Docker images, build caches, and databases are already
there.

<Info>
  CI never creates or destroys environments. A developer provisions the
  environment once. CI only creates and removes workspaces.
</Info>

## Why Use RemoteMux for CI

RemoteMux workspaces skip the setup tax that ephemeral CI runners pay on every run:

| Step                            | GitHub runner          | RemoteMux workspace  |
| ------------------------------- | ---------------------- | -------------------- |
| Checkout repo                   | \~15s                  | \~5s (git worktree)  |
| Install dependencies            | 60-120s (cold)         | 10-20s (warm cache)  |
| Start services (Docker Compose) | 120-300s (pull images) | 0s (already running) |
| DB migrations / seed            | 30-60s                 | 0s (already done)    |
| **Total setup**                 | **3-8 min**            | **\~15-30s**         |

This adds up. For a team running 50 PRs/day with a heavy environment, RemoteMux
saves 200+ runner minutes per day. The compute cost is sunk since the
environment is already running for development.

RemoteMux CI is most useful when:

* Your project has a complex dev environment (Docker Compose stacks, databases, GPUs)
* Environment setup time dominates test execution time
* You need VPC access or internal services that runners can't reach
* You want dev/CI parity (tests run in the same environment developers use)

## Setup

<Steps>
  <Step title="Create a CI token">
    Create a service token for the CI runner. This only needs to happen once.

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    rmux token create --label "github-actions" --project-id <project-id>
    ```

    Prefer a project-scoped token for CI so the runner can only access the one hosted project it needs. Use `--org-id <organization-id>` if you need to target an organization other than your active default.

    Copy the token value for the next step.
  </Step>

  <Step title="Add GitHub secrets">
    In your repo settings, add two secrets:

    | Secret             | Value                                                          |
    | ------------------ | -------------------------------------------------------------- |
    | `RMUX_CI_TOKEN`    | The token from step 1                                          |
    | `RMUX_ENVIRONMENT` | Environment name (e.g. `my-project`) or ID (e.g. `env_abc123`) |

    The environment must already be provisioned and in `ready` state.
  </Step>

  <Step title="Make config available to the runner">
    The runner still needs RemoteMux config so the CLI can resolve the backend and control plane URL.

    Choose one:

    * Commit a minimal `./.rmux.toml` to the repository:

      ```toml theme={"theme":{"light":"github-light","dark":"github-dark"}}
      backend = "managed"
      apiBaseUrl = "https://api.remotemux.com"
      ```

    * Or set these workflow environment variables:

      ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
      RMUX_BACKEND: managed
      RMUX_API_BASE_URL: https://api.remotemux.com
      ```

    `RMUX_ENVIRONMENT` tells the runner which environment to target, but it does not replace backend or API base URL config.
  </Step>

  <Step title="Add the workflow">
    Create `.github/workflows/test.yml` in your repository.
  </Step>
</Steps>

## Example Workflow

```yaml .github/workflows/test.yml theme={"theme":{"light":"github-light","dark":"github-dark"}}
name: Test
on:
  pull_request:
    types: [opened, synchronize, reopened]

concurrency:
  group: rmux-${{ github.event.pull_request.number }}
  cancel-in-progress: true

env:
  RMUX_BACKEND: managed
  RMUX_API_BASE_URL: https://api.remotemux.com
  RMUX_API_KEY: ${{ secrets.RMUX_CI_TOKEN }}
  RMUX_ENVIRONMENT: ${{ secrets.RMUX_ENVIRONMENT }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install RemoteMux
        run: curl -fsSL https://remotemux.com/releases/install.sh | bash

      - name: Create workspace
        run: rmux workspace new "ci-pr-${{ github.event.pull_request.number }}"

      - name: Run tests
        run: |
          rmux workspace run -w "ci-pr-${{ github.event.pull_request.number }}" \
            "cd /workspace && npm ci && npm test"

      - name: Cleanup
        if: always()
        run: rmux workspace rm "ci-pr-${{ github.event.pull_request.number }}" --force
```

Each `workspace new` creates a git worktree on the remote environment. Multiple
PRs can run concurrently since each workspace is isolated at the filesystem
level.

If your repository already includes a minimal `.rmux.toml` with `backend` and
`apiBaseUrl`, you can omit `RMUX_BACKEND` and `RMUX_API_BASE_URL` from the
workflow `env` block.

## Config Resolution In CI

RemoteMux resolves config separately from environment targeting:

* `RMUX_BACKEND` or `./.rmux.toml` selects the backend
* `RMUX_API_BASE_URL` or `./.rmux.toml` selects the control plane URL
* `RMUX_API_KEY` authenticates the runner
* `RMUX_ENVIRONMENT` selects the target environment

On a fresh GitHub runner with no checked-in `.rmux.toml`, set all four in the
job environment.

## Environment Resolution

Workspace commands resolve the target environment in this order:

1. `--env` flag (explicit override)
2. `RMUX_ENVIRONMENT` env var
3. `.rmux/state.json` directory binding (local dev)

In CI, there is no directory binding. Set `RMUX_ENVIRONMENT` once in the
workflow `env` block and all workspace commands pick it up.

You can also override per-command:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
rmux workspace ls --env staging
rmux workspace new ci-run --env my-other-project
```

Both environment names and IDs (`env_*`) are accepted.

## Token Scope

Service tokens are scoped to the hosted organization and, optionally, a hosted project.

* Project-scoped tokens are the safest default for CI.
* Organization-scoped tokens are useful when one workflow needs to operate across multiple hosted projects in the same organization.
* CI workspace commands still target a concrete environment through `RMUX_ENVIRONMENT`; token scope controls what the runner is allowed to see.

## Cleaning Up Stale Workspaces

If a CI runner is killed or GitHub has an outage, the cleanup step may not run.
Use `workspace prune` to remove orphaned workspaces.

### Manual prune

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
# See what would be removed
rmux workspace prune --prefix "ci-" --stale 2h --dry-run

# Remove them
rmux workspace prune --prefix "ci-" --stale 2h --force
```

### Scheduled prune

Add a cron workflow to your repository:

```yaml .github/workflows/prune.yml theme={"theme":{"light":"github-light","dark":"github-dark"}}
name: Prune stale CI workspaces
on:
  schedule:
    - cron: '0 */6 * * *'

env:
  RMUX_BACKEND: managed
  RMUX_API_BASE_URL: https://api.remotemux.com
  RMUX_API_KEY: ${{ secrets.RMUX_CI_TOKEN }}
  RMUX_ENVIRONMENT: ${{ secrets.RMUX_ENVIRONMENT }}

jobs:
  prune:
    runs-on: ubuntu-latest
    steps:
      - name: Install RemoteMux
        run: curl -fsSL https://remotemux.com/releases/install.sh | bash

      - name: Prune stale workspaces
        run: rmux workspace prune --prefix "ci-" --stale 2h --force
```

### Prune options

| Option               | Description                                                                    |
| -------------------- | ------------------------------------------------------------------------------ |
| `--prefix <prefix>`  | Only prune workspaces whose name starts with this prefix                       |
| `--stale <duration>` | Only prune workspaces idle longer than this duration (`30s`, `5m`, `2h`, `1d`) |
| `--dry-run`          | List candidates without deleting                                               |
| `-f, --force`        | Skip the confirmation prompt (required in non-TTY / CI)                        |

Root workspaces are never pruned regardless of filters.

## Concurrency

Multiple CI workspaces share a single VM. They are isolated at the filesystem
level (separate git worktrees) but share CPU, memory, disk, and the Docker
daemon.

Shared resources can be an advantage: Docker layer caches, `node_modules`
caches, and pre-started services are available to all workspaces without
redundant setup.

If concurrent CI runs overwhelm the VM, size the environment appropriately or
use GitHub Actions `concurrency` groups to limit parallel runs.
