GitHub Actions Runner on IncusOS VM¶
Deploy a GitHub Actions runner on an Ubuntu VM on IncusOS using task runner:instantiate. The task creates the VM, sets up the runner user, and installs and registers the GitHub Actions runner.
Prerequisites¶
- IncusOS server installed and running (see IncusOS Server)
- Incus CLI on your machine, remote configured
- Workspace initialized and context set (see Initialize Workspace)
- GitHub repo or org access and a runner registration token
Step 1: Create or Set the Context¶
First, navigate to your workspace directory. Then either create a new context or switch to an existing one:
To create a new context (e.g., for a new environment or project):
cd /path/to/your/workspace
windsor init <context>
To use an existing context (if you already have a context defined):
cd /path/to/your/workspace
windsor context set <context>
Runner configuration will be read from contexts/<context>/windsor.yaml for whichever context is active.
Step 2: Configure Environment¶
Add to contexts/<context>/windsor.yaml:
environment:
VM_INSTANCE_NAME: github-runner
VM_MEMORY: 4GB
VM_CPU: 4
VM_AUTOSTART: true
VM_NETWORK_NAME: eno1
VM_DISK_SIZE: 50GB
DOCKER_HOST: unix:///var/run/docker.sock
RUNNER_USER: "runner"
RUNNER_HOME: "/home/runner"
GITHUB_RUNNER_REPO_URL: "https://github.com/<org-or-user>/<repo>"
GITHUB_RUNNER_TOKEN: "<runner-token>"
Optional: GITHUB_RUNNER_VERSION, GITHUB_RUNNER_ARCH (default x64). For SOPS: put the token in secrets.yaml, encrypt with task sops:encrypt-secrets-file, and set GITHUB_RUNNER_TOKEN: sops.GITHUB_RUNNER_TOKEN in windsor.yaml.
Step 3: Get GitHub Runner Token¶
- In GitHub: Settings → Actions → Runners → New self-hosted runner
- Choose Linux, x64, and copy the registration token.
- Set
GITHUB_RUNNER_TOKENinwindsor.yaml(or in SOPS secrets as above).
The token is short-lived; use it when you run instantiate.
Step 4: Verify Remote¶
incus remote list
incus list <remote-name>:
windsor env | grep INCUS_REMOTE_NAME
Step 5: Create the Runner VM¶
task runner:instantiate -- <remote-name> [<runner-name>] [--keep]
<remote-name>(required): Incus remote (e.g.nuc)<runner-name>(optional): VM name (default:runner)--keep: Do not destroy VM after creation (use for real deployments)
Instantiate will: verify remote, create the VM with vm:instantiate, set up the runner user, install and configure the GitHub Actions runner, and start the service. On first run you may be prompted for repo URL and token if not in env/secrets.
Step 6: Verify¶
- In GitHub: Settings → Actions → Runners — runner should appear with a green status.
- On the VM:
incus exec $INCUS_REMOTE_NAME:<runner-name> -- sudo systemctl status actions.runner.*.service
Use runs-on: self-hosted in workflows to target this runner.
Managing the Runner¶
task runner:status -- <runner-name>
incus start $INCUS_REMOTE_NAME:<runner-name>
incus stop $INCUS_REMOTE_NAME:<runner-name>
To access the VM: get IP from incus list $INCUS_REMOTE_NAME:<runner-name> and SSH as your user, or use incus exec $INCUS_REMOTE_NAME:<runner-name> -- bash.
Destroying the Runner VM¶
task runner:destroy -- <runner-name>
This stops the runner service, unregisters it from GitHub, and destroys the VM. Use runner:destroy (not only vm:destroy) so the runner is removed from GitHub.
INCUS_TRUST_TOKEN (for runners that run Incus VM tests)¶
When this runner executes workflows that create VMs and configure Incus remotes (e.g., Incus VM Tests), store the Incus trust token in the runner's environment so the action gets it from the device it's running on.
Generate the token on the Incus server (or from a host with Incus access):
incus config trust add <client-name>
# Copy the token from the output
Set on the runner (choose one):
- systemd service: Add
Environment="INCUS_TRUST_TOKEN=<token>"to the runner's service file - Runner .env: Add
INCUS_TRUST_TOKEN=<token>to.envin the runner directory (if your runner loads it) - GitHub secret: Add
INCUS_TRUST_TOKENas a repo secret; the workflow passessecrets.INCUS_TRUST_TOKENto the action
The script prefers INCUS_TRUST_TOKEN from the environment over generating a token at runtime.
Troubleshooting¶
- Runner not in GitHub: Check token is correct and not expired; check logs:
incus exec $INCUS_REMOTE_NAME:<runner-name> -- sudo journalctl -u actions.runner.*.service -n 50 - Service not starting:
incus exec $INCUS_REMOTE_NAME:<runner-name> -- sudo systemctl status actions.runner.*.service - VM not starting:
incus list $INCUS_REMOTE_NAME:,incus console $INCUS_REMOTE_NAME:<runner-name>
Related¶
- IncusOS Server
- Ubuntu VMs
- VM Taskfile (includes
--runnerandinstantiate:add-runner-if-requested)