actions/scaleset client to receive jobs and desired runner capacity. The same controller image can run in two places. In Novita Hosted mode, the recommended default, the controller runs in a long-running Control Sandbox on Novita, so it stays online without depending on your machine. In Local Docker mode, the controller runs on a machine you manage and still creates ephemeral runner sandboxes on Novita.
The controller uses GitHub’s Scale Set API, which GitHub currently labels Public Preview. The client API is stable, but its Go interfaces may evolve.
How it works
Hosted mode uses two public Novita templates, so there is nothing to build before deploying:
Each job’s just-in-time (JIT) runner configuration reaches the runner sandbox over the adapter’s stdin and the sandbox filesystem API. The runner entrypoint reads and unlinks the file, then exports it only to the runner child process. The configuration never appears in sandbox creation environment values, metadata, process arguments, or controller logs.
Prerequisites
Prepare the following on the machine where you run the deployment script:- Bash and Python 3.
- Node.js 20+ and npm, if the
novitaCLI is not already installed. - The latest
novitaCLI (version 2.1.0 or newer). See CLI Authentication. - A Novita API key. It selects the Novita team that will own the Control Sandbox and brokered Secrets; a separate browser login is not required.
- A GitHub personal access token (PAT) that can manage self-hosted runners:
- For a classic PAT, use
repofor a repository target oradmin:orgfor an organization target. - For a fine-grained PAT, grant repository
Administration: read and write, or organizationAdministration: readplusSelf-hosted runners: read and write.
- For a classic PAT, use
- The exact repository or organization URL to register, such as
https://github.com/novitalabs/github-actions-runner-controller.
CLI
Deploy on Novita (recommended)
1
Export credentials
Export the three required values in the current shell. Do not commit them to the repository.
CLI
GITHUB_CONFIG_URL is mandatory. The script rejects the example OWNER/REPO and OWNER/REPOSITORY placeholders so it cannot silently deploy against an unintended target.Verify that the CLI sees the intended team before deployment:CLI
2
Run the deployment script
Clone the repository and run the Hosted deployment from the repository root:The script runs on your machine and creates the long-running Control Sandbox in Novita. On success it prints the target identity, the Control Sandbox ID, and the health state:Keep this output. The resume script below accepts the sandbox ID it contains.
CLI
3
Point workflows at the runner
The default Scale Set label is For complete workflow files, see the GitHub CI examples in the Novita Sandbox cookbook.If you use a coding agent, you can also ask it to write the workflow for you:The controller creates or reuses the named Scale Set. It intentionally does not delete it on shutdown, so controller restarts are safe.
novita-scale-set. In the target repository, point each job that should run on Novita at this label:.github/workflows/ci.yml
Prompt
Multiple repositories or organizations
One controller serves exactly oneGITHUB_CONFIG_URL target. How many repositories that covers depends on whether the target is a personal repository or an organization.
Personal repositories. A controller registered against a repository URL only serves that repository. To use Novita runners in several personal repositories, run the deployment once per repository. The script derives a stable target ID from the normalized GITHUB_CONFIG_URL, so each repository gets its own controller identity, brokered Secrets, and Control Sandbox, and a second deployment cannot reuse or overwrite the first one.
CLI
CLI
Resume the Control Sandbox
The resume script accepts either the exact sandbox ID or the same GitHub target URL. Target-based lookup checks both running and paused sandboxes and fails on duplicates instead of choosing one arbitrarily.CLI
Alternative: Local Docker
Use Local mode when the controller should run on a machine you manage. Install Docker and prepare the same GitHub PAT and Novita API key described above. Clone the repository and copy the example environment file. The.env file is ignored by Git:
CLI
.env and fill in the target URL and credentials. The remaining values match the defaults and can stay as they are:
.env
CLI
novitalabs/github-actions-controller:v0.1.1 image. Credentials are provided only through GITHUB_TOKEN and NOVITA_API_KEY in .env. Workflows use the same runs-on: novita-scale-set configuration shown above. For every variable the controller accepts, see Configuration.
Custom runner template
To preinstall tools, build your own runner template on top of the public runner image and point the controller at it. See Build Template for the fullnovita template create reference.
1
Write the template Dockerfile
Start from Add further
templates/runner/novita.Dockerfile in the controller repository. Keep the three conventions the controller relies on: the user account, the /home/user/actions-runner directory, and the entrypoint.sh start command. The controller writes each job’s JIT configuration into that directory and the entrypoint consumes it.templates/runner/novita.Dockerfile
apt-get install packages or tool downloads as needed. Install user-level tools such as rustup or nvm while USER user is active so they land in /home/user.2
Build the template
Choose a name that does not collide with the public Rebuild with the same name after each Dockerfile change, or use a new name if the platform rejects a duplicate.
github-actions-runner template. Resources default to 2 vCPU and 4 GB; raise them for heavy compiles.CLI
3
Set NOVITA_RUNNER_TEMPLATE
Hosted mode reads the template name at deployment time. Kill the existing Control Sandbox for the target, then redeploy with the override:Local Docker mode reads it from Workflows do not change. Re-run a job and it lands on the new template.
CLI
.env, then docker compose up -d:.env
SCALE_SET_NAME for each, and pick the toolchain in a workflow with runs-on.
Configuration
Hosted deployment also accepts
CONTROLLER_TEMPLATE, GITHUB_SECRET_NAME, NOVITA_SECRET_NAME, and CONTROL_TIMEOUT. Explicit identity or Secret name overrides are intended for advanced operation; the per-target defaults are safer for normal multi-project deployments. CONTROL_TIMEOUT controls the lifetime used during deployment and by scripts/resume-hosted.sh.
For lifecycle ownership and failure boundaries, see the project’s design notes.