Saltar al contenido

Docs / CI autoalojada

Self-hosted CI runners

Replace Blacksmith or Actions-hosted runners with your own VPS. Change one line in your workflow (runs-on: developerz-2vcpu) and jobs run on your hardware.

developerz.ai turns your VMs into JIT (just-in-time) GitHub Actions runners. Change one line in any workflow file:

jobs:
  test:
    runs-on: developerz-2vcpu  # was: ubuntu-latest

That's it. The job runs on your own hardware, logs are stored for 72 hours, and your billing is whatever your VPS costs, not GitHub's metered per-minute runner fees. See the GitHub billing note below for the one private-repo exception, with current date-stamped terms.

Prerequisites

  • A box enrolled in your fleet (see quickstart).
  • The GitHub App installed on the repo.
  • The box advertises the ci capability. Nothing to configure: a box advertises it on its next setup run if a container runtime answers there, and the enrolment script installs one. A box where none does (no Docker, or a daemon the runner cannot reach) advertises nothing and never claims a CI job; the job stays queued instead. Confirm the capability landed with box fleet_status in the MCP before you change a runs-on: line, and talk to us if it does not appear.

Runner labels

A label is a size request, and the size it requests is a floor the assigner checks against the box's reported hardware. The grammar is developerz-{N}vcpu for any positive whole N, plus the bare alias developerz (4 vCPU). Every requested vCPU carries 4 GiB of RAM.

Label vCPU RAM floor Notes
developerz 4 16 GiB The bare alias, for a job that does not care
developerz-2vcpu 2 8 GiB Standard workloads
developerz-4vcpu 4 16 GiB Build-heavy / parallel tests
developerz-8vcpu 8 32 GiB Large monorepos

Those four are illustrative, not a closed list: developerz-16vcpu works the same way. Size the box for the label, not the other way round. The check is cores >= N and RAM >= N x 4 GiB, and it fails closed: a 2-vCPU box with 4 GB of RAM does not satisfy developerz-2vcpu, and neither does a box that has never reported its specs. The job then stays queued until a big enough box is free or the workflow timeout fires, and the assigner logs the label-match failure in the audit trail.

Two things to know before you invent a label:

  • A malformed size label refuses the whole job, rather than being guessed at. developerz-0vcpu, developerz-2cpu and developerz-2vcpu-arm are all refused, because GitHub only dispatches to a runner advertising every requested label, so claiming a size we cannot honour would just fail the job later and less clearly.
  • There are no GPU or architecture labels today. A developerz- label that is not a size request is treated as a hard capability requirement matched against what your box advertises, and the capability vocabulary has no gpu and no arm64 member, so such a job would queue forever. Boxes are Ubuntu x64 by the enrollment contract.

Security model

Scoped installation token

Each CI job receives a short-lived GitHub installation token scoped to the repo the job belongs to. The token is:

  • Minted just-in-time by the API when the job is assigned.
  • Valid for 15 minutes (standard GitHub installation token TTL).
  • Revoked on job completion (best-effort; GitHub expires it anyway).

The box never holds the GitHub App private key, only the API does.

Fork PR policy

Jobs triggered by fork pull requests never run on fleet boxes. A fork PR can carry an attacker-controlled workflow file, so the assigner refuses these jobs before they are ever placed on your hardware, there is no policy key to opt in. Run fork-PR checks on GitHub-hosted runners instead.

The related repo-wide fence is ci.public_repos in .maintainer.yml (default false): even for same-repo PRs, CI on a public repo stays off until a maintainer opts in, because a public repo lets anyone open a PR whose workflow runs on your hardware.

# .maintainer.yml
ci:
  enabled: true          # default: true, kill switch for the whole CI lane
  public_repos: false    # default: false, must be true to run CI on a public repo

No App credentials on the box

The GitHub App private key is never placed on a runner. Boxes talk to the API over an authenticated control channel; the API holds all secrets and hands each job only its scoped, short-lived token.

Ephemeral containers

Each CI job runs inside an isolated container (Docker or Podman). The container is created fresh for the job and destroyed when it completes. No state persists between jobs on the same box.

Log retention

Logs are streamed to object storage (your BYO S3 or the platform bucket) in real time, compressed with zstd, and retained for 72 hours. After 72 hours the reaper deletes them automatically.

To download a log:

# MCP verb
ci log_url --ci-job-id <uuid>
# → { url: "https://…presigned…", expires_in_seconds: 3600 }

Or from the dashboard under Fleet → CI.

Idle AI devs as CI runners

Boxes that run AI coding tasks (the worker runner mode) also serve CI jobs when idle. The assigner checks box presence and capacity before routing. This means your AI dev fleet doubles as CI infrastructure with no additional cost, the hardware is already running.

GitHub billing note

Private repos only. GitHub charges $0.002/minute for self-hosted runner minutes on private repositories (2026-03 terms, verify current terms before pricing). Public / OSS repositories are exempt. The developerz.ai platform fee does not include this charge; it appears on your GitHub bill. Check github.com/pricing for the latest terms.

Checking CI run status

# MCP, list recent runs
ci runs --repo owner/repo

# Filter by status
ci runs --status failed --repo owner/repo

# Get log URL
ci log_url --ci-job-id <uuid>

Full verb reference: MCP verb reference → ci.