Skip to content
All pages

GitHub Actions runners

Run GitHub Actions jobs in fresh moatbox spaces by installing the moatbox CI app and labelling your jobs.

moatbox runs GitHub Actions jobs for your team. Each job gets a fresh space, runs there, and moatbox deletes the space when the job ends. You pick a size with the job's runs-on label.

Connect GitHub

An owner or admin connects GitHub from a signed-in browser session.

  1. Open CI in the dashboard and press Connect GitHub.
  2. On GitHub, install the moatbox CI app on your account or organization and pick its repositories.
  3. GitHub sends you back to the CI page, which shows the linked account.

moatbox checks that you may link that installation. For an organization, you must be an active admin of it. For a personal account, it must be the account you signed in to GitHub with. The connect link expires after 30 minutes.

One GitHub account links to one moatbox team. If another team already linked it, the CI page says so.

To add another account or change which repositories the app sees, use the app's settings on GitHub, then come back through Connect GitHub. The Repositories section lists every repository the app can reach and marks each one private or public.

Unlink under GitHub accounts makes moatbox refuse that account's new and queued jobs. Jobs already running finish. The app stays installed on GitHub. Uninstalling the app on GitHub unlinks it too.

Label your jobs

Set runs-on to a moatbox label:

jobs:
  test:
    runs-on: moatbox
    steps:
      - uses: actions/checkout@v5
      - run: make test
Label vCPU Memory
moatbox-small 2 2 GB
moatbox 4 8 GB
moatbox-large 8 16 GB
moatbox-xlarge 16 32 GB

Your plan caps the size. The Use it in a workflow section on the CI page marks the labels your plan does not allow, and moatbox refuses those jobs.

Rules for labels:

  • A job takes one moatbox label, no more.
  • Besides it, a job may list only self-hosted, linux and x64.
  • moatbox ignores jobs without a moatbox label.

Options

Add options after a slash:

Option Does
/from=<name> Start the job's space from the team's saved snapshot with that name. Private repositories only.
/debug Keep the space 30 minutes after the job ends, so you can SSH in

Combine them with another slash or a comma:

runs-on: moatbox-large/from=ci-base/debug

With from, moatbox makes the space on the server that holds the snapshot, and the job waits if that server has no room. Saved snapshots are covered in Snapshots.

Public and private repositories

Jobs from private repositories run once the account is linked.

Each linked GitHub account has a setting for its public repositories, under GitHub accounts on the CI page:

Setting Effect
Public repositories off moatbox refuses jobs from public repositories. This is the default.
Listed GitHub users moatbox runs a job only if both the user who started the run and the user who re-ran it are on the list. Up to 200 usernames.
Anyone Any GitHub user who opens a pull request on a public repository can run code on a moatbox space billed to your team.

Owners and admins change it. from= never works for public repositories.

What a job's space gets

  • A new space named ci-<repo>-<job>-<attempt>, sized by the label. <repo> comes from the repository name: lowercased, each run of characters outside a-z0-9 replaced by one -, cut to 14 characters with no - at either end, or job when nothing is left. <job> is GitHub's job id in base 36.
  • Open network access, including the GitHub hosts the runner needs.
  • No team environment, no team credentials and no AI credentials. Use GitHub secrets. With from=, the space holds every file in the saved snapshot, credential files included. moatbox first removes what it keeps for the source space: its integration credentials, environment, SSH and Tailscale keys, and audit log.
  • No automatic snapshots every 15 seconds, and no sleep when idle.

The space counts toward your plan's limit on boxes running at once, like any other space.

When moatbox refuses or holds a job

A job waits in the queue when your plan's running boxes are all in use, when no server has room, or when the team hit its spend cap or has an unpaid balance. It starts by itself once there is room. After 1 hour of waiting, moatbox refuses it.

moatbox also refuses a job when:

  • the GitHub account is not linked to a moatbox team
  • the team has no active plan
  • the label breaks the rules above, or the plan does not allow the size
  • the repository is public and the setting does not allow the user
  • from= names a saved snapshot the team does not have

When moatbox refuses a job, it adds a failed check named moatbox CI with the title moatbox did not run this job and the reason, then cancels the workflow run.

A job stops at the 6-hour job limit and ends as timed_out. The clock starts when moatbox begins making the job's space.

The times on this page are defaults. The CI page shows the job limit, the queue wait and the debug keep time that apply to your team.

Jobs and logs in the dashboard

The Jobs section on the CI page lists every job. Search by repository, workflow, job or branch, and filter by state:

State Meaning
Queued or waiting moatbox has the job and waits for room, or retries after a failed start
Starting The space is being made and the runner is starting
Running The runner took the job
Done The job ended; the label shows passed, failed, cancelled, skipped or timed out
Refused moatbox did not run it; the reason shows next to it

Each row shows how long the job queued and how long it ran. Open a job to see its Runner log. While the space exists, the log streams live from it. When the job ends, moatbox saves the last 64 KiB of it. The space keeps only the first 8 MiB of runner output, so for a longer job both the live log and the saved 64 KiB end at that point. Under zero data retention it saves nothing, and the output goes with the space. See Privacy.

Debug a job over SSH

With /debug, the job page shows how long the space stays. Reach it with:

moatbox connect ci-myrepo-abc123-1 --for ssh --write
ssh moatbox-ci-myrepo-abc123-1

Use the space name from the job page. The CLI is covered in Connect to a space.

Standby runners

moatbox can keep a ready runner waiting for the next job of a busy private repository, so the job skips the space start. This is a service setting. When it is off, every job gets a new space.

When it is on, a standby:

  • serves one repository and one label, for private repositories only, and never for jobs with from= or /debug
  • exists only while jobs of that repository and label keep coming: one in the last 15 minutes, by default
  • leaves when no job came in that time, when a queued job needs the room, when the repository turns public, or after 6 hours
  • needs a running box your plan has free, and leaves when your own spaces need it

API

All routes live under https://app.moatbox.dev/api/v1. Reading needs the team.read key action. Connecting, unlinking and changing the public setting need an owner or admin in a signed-in browser session.

Method and path Does
GET /teams/{team}/ci Linked accounts, labels and what the plan allows, limits, this period's jobs
GET /teams/{team}/ci/repos Repositories the app can reach
GET /teams/{team}/ci/jobs Jobs. Query: repo, state, q, limit (up to 500, 100 by default), offset
GET /teams/{team}/ci/jobs/{id} One job
GET /teams/{team}/ci/jobs/{id}/log?offset=0 The runner log from offset
POST /teams/{team}/ci/connect Start linking a GitHub account; returns the GitHub install URL
PUT /teams/{team}/ci/installations/{id}/public {"mode": "off" | "users" | "anyone", "users": ["..."]}
DELETE /teams/{team}/ci/installations/{id} Unlink an account
curl "https://app.moatbox.dev/api/v1/teams/$TEAM_ID/ci/jobs?state=refused&limit=20" \
  -H "Authorization: Bearer $MOATBOX_API_KEY"