Ephemeral runners
Every runner Runaway spawns is a one-shot container. It registers with GitHub,
picks up exactly one job, runs it, and then exits. On exit the container is
destroyed (AutoRemove: true, the API equivalent of docker run --rm), and a
fresh container takes its place. Nothing carries over from one job to the next.
The footgun this fixes
Section titled “The footgun this fixes”The naive way to keep a self-hosted runner alive is restart: unless-stopped
in a Compose file. It works for an afternoon, then rots: Docker restarts the
same container after each job, so its writeable layer accumulates state run
after run. Leftover checkout dirs, half-installed toolchains, growing logs, a
filesystem that drifts further from clean every time. Eventually a job fails
for a reason that has nothing to do with the job — it inherited the mess from
the last one.
AutoRemove: true is the only correct way to get true ephemeral behaviour with
Docker. The container is gone the instant the job ends; there is no writeable
layer to accumulate anything. Every job starts from the same known-clean image.
Who decides to replace it
Section titled “Who decides to replace it”You never spawn or destroy a runner by hand. When a runner exits, the reconciliation loop notices the gap between the pool you asked for and the pool that exists, and spawns a replacement. That single path owns every container’s birth and death, keeping the “one job, then gone” guarantee airtight across crashes and restarts.
What does persist
Section titled “What does persist”The container is disposable; your caches are not. Each
scale set mounts a persistent /cache volume that
outlives every runner generation. Point your package managers and browser
binaries at it and warm caches survive across runs without GitHub’s Actions
Cache round-trip.