Four GitHub runners. One endpoint. No extra VMs.

How FlowRMM turned a stalled lab CI queue into a four-process runner pool on a host we already managed.
Self-hosted CI is supposed to be the fast path. In practice it is often one Linux box and a queue that only moves when that box is free.
That is where we were this afternoon. Trusted PR Validation for FlowRMM needed lab-trusted-preflight, then validate-linux-lab. The preflight job sat waiting for a runner, then ran. The next job sat again. Same labels. Same host. One process.
The instinct is to "add more VMs." We already knew better. The Linux CI host is not a rack of hypervisors. It is a Hyper-V guest we already manage in FlowRMM. The last time we needed parallelism, we did not clone the machine. We ran more runner processes on it.
This post is the operator version of that afternoon, and why FlowRMM is the control plane we want in front of work like this.
The stall was not GitHub. It was concurrency one.
The job asked for:
self-hosted, Linux, X64, flowrmm-ci, flowrmm-lab-linux
GitHub had exactly one match: flowrmm-lab-linux-01. It was busy. Everything else with those labels waited.
Windows lab runners were idle. They could not take Linux container jobs. An older ARM64 pool we used to run on a Jetson (Elroy) is retired on purpose. Our trusted Linux boundary is X64, repository-scoped, and fail-closed. Forks stay on GitHub-hosted. Same-repo work can use the lab host when it is online and attested.
So the queue was honest. We had one worker.
FlowRMM already knew the computer
The host is runner-vm in our lab portal. Ubuntu 24.04. A Hyper-V guest. Docker is installed because the lab jobs require a job container. The first runner already lived at /opt/actions-runner as user gha-runner, with a systemd unit GitHub could see.
From FlowRMM we did not guess that. We listed the endpoint, read telemetry, confirmed the unit, and confirmed the tarball was still on disk. We also confirmed what it is not: there is no nested hypervisor on that guest. You do not put eight VMs inside a small LinuxRunner VM and call it a pool.
Elroy taught the shape we actually wanted. One trusted machine. Several GitHub Actions processes. Names in an allow-list. Same labels. Each process takes one job. Parallelism is N, not "hope the VM is idle."
FlowRMM's job is not to replace GitHub Actions. It is to make the host visible, the change governed, and the write-up land on the same computer the work happened on.
We added three processes. Justin approved the change.
GitHub's runner docs already allowed flowrmm-lab-linux-01 through 99. We did not need a workflow rewrite. We needed three more processes with the same labels.
That is a real change on a real endpoint: extract the runner, register it, install a systemd unit, start it. FlowRMM classified it as a mutation and queued it for approval. That is the product, not a speed bump. The same pane that showed the host online is the pane that asked a human to let us touch it.
After approval:
flowrmm-lab-linux-02came up- then
03 - then
04
02 picked up a queued validate job immediately. 01 kept the job it already had. 03 and 04 sat idle, ready.
Four online runners. Same labels. Same computer.
That is the difference between "we should add capacity someday" and "the next PR is not stuck behind one process."
Why this is a FlowRMM story
Most RMM tools will show you that a Linux box is online. Some will let you run a script. Very few will keep the whole loop in one place:
See the endpoint. Not a spreadsheet of VMs. The actual computer, with hardware, disk, and whether the agent is healthy.
Decide on that computer. We almost built VMs. The inventory said guest, not host. The live facts said process pool. That decision is cheaper when the source of truth is the endpoint, not a hallway guess.
Change it with a human in the loop. Installing runners is not a read. It gets an action, a reason, and an approve. The transcript is the change record.
Write the knowledge back to the same computer. After the pool was up, we queued an Ops Knowledge article on runner-vm itself: names, paths, labels, Hyper-V parent, how to add 05+, and what not to run (the retired ARM64 Elroy installer). The next technician does not have to reconstruct this from Slack.
That last part matters more than the systemd units. Capacity that only lives in one person's head is not capacity. Capacity that is tied to the endpoint is.
What we did not do
We did not nest VMs on the guest.
We did not put personal keys, signing certs, or customer tenants on the CI host. This machine is a trusted build boundary for our own repo. Job containers are for reproducibility, not a security story against ourselves.
We did not pretend four concurrent container jobs are free. The guest is still a small VM. Lab preflight wants tens of gigabytes of workspace. If four heavy jobs run at once, disk and RAM will tell the truth. The next hardware move is resize the guest on the Hyper-V host, not invent a second product.
We did not "automate" the approve step. If your RMM can silently mutate a CI host, that is not a feature we want.
The MSP version
If you run customer endpoints, you already have this class of problem. A server is "up." A queue is still stuck. Someone wants more VMs. The real fix is usually one more process, one more service, one more documented exception, on a machine you already manage.
FlowRMM is built for that loop:
- the computer is the object
- the action is classified
- a person approves the write
- the result is verified
- the article lives on the endpoint
Today that computer happened to host GitHub Actions. Tomorrow it is a print server, a SQL box, or the only Linux jump host in a rack. The motion is the same.
We went from one busy runner to four online ones without leaving the endpoint we already trusted.
If you want CI that does not stall, add processes.
If you want that change to be governable, put FlowRMM in front of the host.
Lab environment, 28 August 2026. Repository-scoped runners on flowdevs-io/FlowRMM. Host: runner-vm (LinuxRunner). Pool: flowrmm-lab-linux-01 through 04.
Frequently asked questions
Did you add more virtual machines?
No. The Linux CI host is already a Hyper-V guest. We added GitHub Actions runner processes on that same computer.
Who is the author for Webflow?
Justin Trantham.
Why use FlowRMM for a CI host?
So the endpoint is visible, the change is approved, the result is verified, and the article is tied to that computer.
Sources
- About self-hosted runners (GitHub Docs)
How FlowRMM turned a stalled lab CI queue into a four-process runner pool on a host we already managed.
Self-hosted CI is supposed to be the fast path. In practice it is often one Linux box and a queue that only moves when that box is free.
That is where we were this afternoon. Trusted PR Validation for FlowRMM needed lab-trusted-preflight, then validate-linux-lab. The preflight job sat waiting for a runner, then ran. The next job sat again. Same labels. Same host. One process.
The instinct is to "add more VMs." We already knew better. The Linux CI host is not a rack of hypervisors. It is a Hyper-V guest we already manage in FlowRMM. The last time we needed parallelism, we did not clone the machine. We ran more runner processes on it.
This post is the operator version of that afternoon, and why FlowRMM is the control plane we want in front of work like this.
The stall was not GitHub. It was concurrency one.
The job asked for:
self-hosted, Linux, X64, flowrmm-ci, flowrmm-lab-linux
GitHub had exactly one match: flowrmm-lab-linux-01. It was busy. Everything else with those labels waited.
Windows lab runners were idle. They could not take Linux container jobs. An older ARM64 pool we used to run on a Jetson (Elroy) is retired on purpose. Our trusted Linux boundary is X64, repository-scoped, and fail-closed. Forks stay on GitHub-hosted. Same-repo work can use the lab host when it is online and attested.
So the queue was honest. We had one worker.
FlowRMM already knew the computer
The host is runner-vm in our lab portal. Ubuntu 24.04. A Hyper-V guest. Docker is installed because the lab jobs require a job container. The first runner already lived at /opt/actions-runner as user gha-runner, with a systemd unit GitHub could see.
From FlowRMM we did not guess that. We listed the endpoint, read telemetry, confirmed the unit, and confirmed the tarball was still on disk. We also confirmed what it is not: there is no nested hypervisor on that guest. You do not put eight VMs inside a small LinuxRunner VM and call it a pool.
Elroy taught the shape we actually wanted. One trusted machine. Several GitHub Actions processes. Names in an allow-list. Same labels. Each process takes one job. Parallelism is N, not "hope the VM is idle."
FlowRMM's job is not to replace GitHub Actions. It is to make the host visible, the change governed, and the write-up land on the same computer the work happened on.
We added three processes. Justin approved the change.
GitHub's runner docs already allowed flowrmm-lab-linux-01 through 99. We did not need a workflow rewrite. We needed three more processes with the same labels.
That is a real change on a real endpoint: extract the runner, register it, install a systemd unit, start it. FlowRMM classified it as a mutation and queued it for approval. That is the product, not a speed bump. The same pane that showed the host online is the pane that asked a human to let us touch it.
After approval:
flowrmm-lab-linux-02came up- then
03 - then
04
02 picked up a queued validate job immediately. 01 kept the job it already had. 03 and 04 sat idle, ready.
Four online runners. Same labels. Same computer.
That is the difference between "we should add capacity someday" and "the next PR is not stuck behind one process."
Why this is a FlowRMM story
Most RMM tools will show you that a Linux box is online. Some will let you run a script. Very few will keep the whole loop in one place:
See the endpoint. Not a spreadsheet of VMs. The actual computer, with hardware, disk, and whether the agent is healthy.
Decide on that computer. We almost built VMs. The inventory said guest, not host. The live facts said process pool. That decision is cheaper when the source of truth is the endpoint, not a hallway guess.
Change it with a human in the loop. Installing runners is not a read. It gets an action, a reason, and an approve. The transcript is the change record.
Write the knowledge back to the same computer. After the pool was up, we queued an Ops Knowledge article on runner-vm itself: names, paths, labels, Hyper-V parent, how to add 05+, and what not to run (the retired ARM64 Elroy installer). The next technician does not have to reconstruct this from Slack.
That last part matters more than the systemd units. Capacity that only lives in one person's head is not capacity. Capacity that is tied to the endpoint is.
What we did not do
We did not nest VMs on the guest.
We did not put personal keys, signing certs, or customer tenants on the CI host. This machine is a trusted build boundary for our own repo. Job containers are for reproducibility, not a security story against ourselves.
We did not pretend four concurrent container jobs are free. The guest is still a small VM. Lab preflight wants tens of gigabytes of workspace. If four heavy jobs run at once, disk and RAM will tell the truth. The next hardware move is resize the guest on the Hyper-V host, not invent a second product.
We did not "automate" the approve step. If your RMM can silently mutate a CI host, that is not a feature we want.
The MSP version
If you run customer endpoints, you already have this class of problem. A server is "up." A queue is still stuck. Someone wants more VMs. The real fix is usually one more process, one more service, one more documented exception, on a machine you already manage.
FlowRMM is built for that loop:
- the computer is the object
- the action is classified
- a person approves the write
- the result is verified
- the article lives on the endpoint
Today that computer happened to host GitHub Actions. Tomorrow it is a print server, a SQL box, or the only Linux jump host in a rack. The motion is the same.
We went from one busy runner to four online ones without leaving the endpoint we already trusted.
If you want CI that does not stall, add processes.
If you want that change to be governable, put FlowRMM in front of the host.
Lab environment, 28 August 2026. Repository-scoped runners on flowdevs-io/FlowRMM. Host: runner-vm (LinuxRunner). Pool: flowrmm-lab-linux-01 through 04.
Frequently asked questions
Did you add more virtual machines?
No. The Linux CI host is already a Hyper-V guest. We added GitHub Actions runner processes on that same computer.
Who is the author for Webflow?
Justin Trantham.
Why use FlowRMM for a CI host?
So the endpoint is visible, the change is approved, the result is verified, and the article is tied to that computer.
Sources
- About self-hosted runners (GitHub Docs)




