spotfleet
Spot GitHub Actions runners for each client, one job per machine, at zero when no job waits. A console shows every fleet, changes it, and states its cost in euros and US dollars.
Key metrics
Architecture
GitHub publishes no queue metric, so a scaler runs once a minute from EventBridge, counts the queued jobs with the fleet label through a GitHub App, writes a one-hour runner token into a new launch template version and raises the Auto Scaling target. Each spot machine registers one ephemeral runner, takes one job, and ends itself; only a machine lowers the target, and a nightly floor at 02:00 UTC sets it to zero. Each fleet has its own security group, roles, encrypted gp3 volumes and token scope, and the console reads cost by tag from Cost Explorer at the ECB rate of each day.
Case study
spotfleet
A CI job that waits for a runner costs time; a runner that waits for a job costs money. spotfleet runs GitHub Actions jobs on AWS spot machines that exist only while a job needs them, one fleet for each client. The console is live at thecatthatcodes.com.
How a fleet works
GitHub publishes no queue metric, so a scaler polls.
- An EventBridge rule calls the fleet's scaler once a minute.
- The scaler counts the queued jobs with the fleet label, through the GitHub App.
- When jobs wait, the scaler writes a one-hour runner token into a new launch template version and raises the Auto Scaling target. The standing credential never leaves the scaler.
- A spot machine boots, attaches a free persistent volume of its fleet, registers one ephemeral runner and takes one job.
- After the job, after 15 idle minutes, or on a spot notice with no job, the machine ends itself and lowers the target.
- A nightly floor at 02:00 UTC sets the target to zero, so a scaler defect costs at most one day.
flowchart LR EB[EventBridge
every minute] --> S[Scaler] S -->|queued jobs?| GH[GitHub App] S -->|1-hour token + target| ASG[Auto Scaling group] ASG --> M[Spot machine
one ephemeral runner] M -->|takes one job| GH M -->|ends itself, lowers target| ASG
| A fleet | What it is |
|---|---|
| Runners | Spot machines, one job each, then the machine ends |
| Size | 0 when no job waits; at most the cap in the fleet file |
| Image | Stock Ubuntu 24.04; the boot installs Docker, the AWS CLI and the pinned runner |
| Persistence | One encrypted gp3 volume per runner slot keeps Docker and the tool cache |
| Isolation | Own security group, roles, volumes and GitHub token scope, every resource tagged by client and fleet |
The console
Each person picks the fleets to watch, sees live sparklines for activity, CPU, memory and disk, and reads "spent today" and "spent this month" as live estimates: machine-seconds times the spot price now, plus the volumes. The billed cost comes from AWS Cost Explorer by tag, converted each day at the European Central Bank reference rate, and each fleet has an AWS budget with an alarm at 80%.
Nobody uses the service until an admin approves them. A person asks for access by email or with GitHub and states an organisation; once approved, they see every fleet of that organisation. A client connects a repository by installing the GitHub App and changing runs-on to the fleet label.
Where it came from
The pattern comes from the CI fleet built for Provely, which spotfleet has since adopted: up to 16 arm64 spot runners, brought in without rebuilding the group, template or credential.
Tech stack
Gallery