Skip to main content
Airship sits between GitHub and your cloud account and does one thing: turns a queued job into a machine, and a finished job into no machine.

Step by step

1

GitHub says a job is queued

A workflow_job webhook arrives. If none of the job’s labels start with airship-, Airship stops here and the job costs nothing. Jobs on ubuntu-latest never touch any of this.
2

Airship reads your config at that commit

airship.yaml is read from the job’s own commit, so a branch can try a new machine shape before it is merged. Organization-wide definitions in .airship are read from that repository’s default branch.If the file does not parse, the job is skipped with a reason naming the field, and the error lands on Events.
3

The job is planned once

Every matching definition becomes an ordered list of attempts — one per combination in its fallback lists, then any other definition sharing the label. The plan is fixed now. Editing the file afterwards does not change how a waiting job is served.
4

A runner slot is claimed

Under a row lock on your installation, Airship checks the live runner count against max_runners. Several control plane replicas cannot both take the last slot.
5

GitHub mints a single-use registration

A just-in-time runner configuration, good for exactly one registration and one job, scoped to that repository. Its GitHub runner ID is stored before the machine is created, so nothing can boot without a record to clean up.Registration takes a round trip. If the job was cancelled or picked up elsewhere in that window, no machine is created and the registration is removed at once.
6

Your cloud launches one instance

Airship opens your sealed credential, makes one API call, and discards it. The machine carries tags identifying the installation, repository and runner, so you can group a bill by repository with no extra instrumentation.On first use in a region, Airship also creates the shared pieces it needs — an egress-only security group, or a network, or a NAT — and reuses them forever after. What Airship creates in your account.
7

The machine boots and takes the job

A startup script schedules its own shutdown deadline first, then installs (or finds) actions/runner, registers with the configuration it was given, and runs as an ephemeral runner. It exits after one job.The runner talks to GitHub directly. Logs, artifacts, secrets and checkouts never pass through Airship.
8

The machine destroys itself

Every exit path powers the machine off: the job finishing, the runner crashing, the script failing, the deadline expiring. Airship then deletes the instance along with the disks, address and interface it created, and removes the GitHub registration two minutes later — but only if GitHub agrees the runner is not busy.

One job per machine

This is the property everything else rests on. A runner is registered for a single job and the machine is destroyed afterwards, so no job can read another’s checkout, caches on disk, environment or in-memory state. There is no pool of warm machines quietly accumulating state between jobs — even idle runners are replaced the moment they take a job.

When things go wrong

Clouds run out of capacity, webhooks arrive out of order, and control planes get restarted mid-launch. The design assumes all three.
The next attempt in the plan is tried. A failure your cloud marks retryable — capacity, throttling — starts the list over, until the job has had three failed launches or one per attempt, whichever is more. A permanent failure such as a missing image stops once every attempt has been tried. Every failure is on Events, with your cloud’s own message.
While queued, no machine is created and the registration is removed. While running, the runner stops, the machine powers off and the instance is deleted. Cancelling a job is the cheapest way to confirm this for yourself.
The runner is marked launch_unconfirmed: the cloud may or may not have made a machine. The job is retried, and an inventory scan either finds the machine and revives the runner or, after a grace period, writes it off. Nothing is left running and nothing is double-charged for long.
Launching runners hold a lease. When it expires another replica takes over, reconciling against what the cloud actually has rather than what the database last wrote.
It runs no job and still bills for its disk, so it is treated as gone: the next inventory pass deletes it and the runner is finished.
Every machine schedules its own shutdown before anything else runs, so nothing runs forever. You may be left with instances to clean up by tag.

Runner states

A launched runner that no job arrives for within ten minutes finishes as idle — almost always a label mismatch. Every machine has a hard lifetime (eight hours by default) after which it finishes as max_lifetime.

Reading the trail

Jobs shows what was planned, Runners shows what was launched, and Events shows everything that failed or quietly degraded along the way.