Spot Instance

A spot instance is spare cloud capacity sold at a discount to on-demand rates, on the condition that the provider can reclaim the machine on short notice.

Definition

A spot instance is spare capacity in a cloud provider's fleet, offered at a discount to that provider's standard on-demand rate on the condition that the provider can reclaim the machine when it wants the capacity back. The hardware, the operating system image, and the instance type are identical to an on-demand instance of the same shape, and the only thing you give up is tenure.

Every large provider sells this model under its own name. AWS calls them Spot Instances, Google Cloud calls them Spot VMs, and Azure calls them Spot Virtual Machines. Google's earlier product for the same idea was the preemptible VM, and that word still appears in older tooling and scripts. "Interruptible" and "preemptible" are the generic adjectives for the model, and "spot" is the word that shows up on the invoice.

Where the spare capacity comes from

A cloud region is built out of pools of physical capacity, separated by instance family, generation, size, and availability zone. On-demand and reserved demand never lines up exactly with what the provider has racked, so at any given moment part of each pool is idle. Spot pricing sells that idle part, priced by the provider from supply and demand inside the individual pool rather than as one figure for the whole region.

Two consequences follow from that, and they are the two that shape any design on top of spot capacity.

First, availability is a per pool property. One instance type in one availability zone can be unavailable while a near identical type in the zone next door has capacity to spare. A request for spot capacity can therefore fail at launch time, before any workload has started, which is a different failure from being reclaimed mid run.

Second, reclamation is a capacity decision rather than a penalty. When on-demand or reserved demand rises inside a pool, the provider takes back what it had lent out. The trigger sits entirely on the provider side, so a workload can prepare for reclamation while having no setting that avoids it.

The interruption contract

Each provider publishes what warning it gives before a spot machine goes away. The windows are short, and the signal is best effort rather than a guarantee.

ProviderProduct nameHow the warning arrivesPublished window
AWSEC2 Spot InstancesSpot Instance interruption notice in instance metadata and as an EventBridge event, sometimes preceded by an instance rebalance recommendation2 minutes
Google CloudSpot VMsPreemption notice delivered as an ACPI shutdown signal, with the state visible in instance metadata30 seconds
AzureSpot Virtual MachinesEviction notice delivered through Scheduled Events on the instance metadata service30 seconds

Sources, each checked on 2026-08-13: AWS Spot Instance interruptions, Google Cloud Spot VMs, and Azure Spot Virtual Machines.

Read those windows as what they are. Thirty seconds to two minutes is enough to flush a log buffer, upload a partial artifact, drain a queue consumer, or deregister an agent. It is nowhere near enough to checkpoint a linker, a test suite, or a container build and resume it somewhere else. Any workload that plans to survive an interruption has to be able to restart from a known point, and the practical version of that for most build and test work is restarting from the beginning.

The second half of the contract is the part people notice later: interruption ends the instance, and everything on its local disk goes with it. Anything the workload wrote to remote storage before the notice arrived is still there. Anything it held in memory or on the ephemeral volume is gone.

Fallback instance selection

Because availability is a per pool property, the standard mitigation is to stop asking for one instance type. A launcher that accepts a list of acceptable types can place the workload wherever capacity exists at that moment, and picks from that list based on availability and price.

Three rules make the list useful rather than decorative:

  • Keep the types close in size and performance. A list that mixes a 4 vCPU type with a 32 vCPU type produces wildly different run times depending on which one wins, and every downstream timeout has to be sized for the slowest case. Types from the same size class in adjacent families, for example m7a.xlarge alongside m7i.xlarge, keep the behavior predictable.
  • Spread the subnets across availability zones. The list of types multiplies with the list of zones, and the product of the two is the real pool the launcher can draw from.
  • Keep an uninterruptible path for the work that cannot tolerate a restart, addressed separately, so moving a workload back is a configuration change rather than a redesign.

The same idea appears in build infrastructure that launches runners inside a cloud account you own. The BYOC setup documentation covers fallback instances directly, and the AWS configuration guide covers the account side, including the subnet spread across availability zones and the EC2 spot permissions the role carries.

Example

GitHub Actions is a clean place to watch the trade play out, because one job runs on exactly one machine and the workflow file says which machine it wants through runs-on. That makes the decision per job rather than per repository.

A sharded unit test job tolerates interruption. Every shard reads the same checkout, writes its results, and produces the same answer if it runs again, so losing one shard costs one shard.

A publish job does not tolerate interruption. It pushes tags, writes release notes, and touches state that lives outside the job, so an interruption halfway through leaves a partly finished release that the retry has to reconcile before it can do its own work.

Written out, the split is one label per job. In a BYOC setup the runner ID carries the warp-custom- prefix, which the BYOC documentation records as a requirement:

.github/workflows/ci.yml
name: ci
on:
  pull_request:
  push:
    branches: [main]

jobs:
  unit-tests:
    runs-on: warp-custom-linux-8x-spot
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - run: make test SHARD=${{ matrix.shard }}

  publish:
    needs: unit-tests
    if: github.ref == 'refs/heads/main'
    runs-on: warp-custom-linux-8x-ondemand
    steps:
      - uses: actions/checkout@v4
      - run: make publish

Two runner configurations sit behind those two labels. One is backed by interruptible capacity and one is not, and the workflow chooses between them one job at a time. fail-fast: false on the matrix keeps a reclaimed shard from cancelling the three siblings that were about to pass.

What happens when the capacity is reclaimed

Say the provider reclaims the machine running shard 3 at the seven minute mark. The sequence is the same on all three clouds, with only the warning window changing:

  1. The provider issues the interruption notice to the instance metadata service and to its event bus.
  2. The instance is stopped or terminated at the end of the published window.
  3. The runner agent on that machine stops answering, and GitHub Actions marks the job as failed once the connection is lost.
  4. The other three shards keep running, because fail-fast: false told the matrix to leave them alone.
  5. A replacement instance can be launched for the retry, chosen from the fallback list rather than from the pool that just ran out.
  6. Shard 3 runs again from its first step. The seven minutes it had already used are still billed by the cloud account that owns the instance.

Step 6 is the whole cost model of spot capacity in one line. The discount is real and the retry is real, and which one wins depends on how long the job is and how often the pool gets reclaimed.

What survives the interruption

The retry starts clean, so the useful question is which side of the instance boundary each piece of state sits on.

StateSurvives an interruptionWhy
Files on the runner's local diskNoThe disk is part of the instance and goes away with it.
Steps already completed in the jobNoThe retry starts the job again from its first step.
Cache entries already written to remote storageYesThey live in object storage outside the instance.
Artifacts already uploaded by actions/upload-artifactYesUpload completed before the notice arrived.
Image layers already pushed to a registry by digestYesContent addressed pushes are idempotent, so the retry writes the same digest.
A partly applied database migrationThe side effect survivesThis is the reason migration jobs stay off interruptible capacity.

Reading the table row by row is a quick eligibility test for any job. If every row that applies to the job lands in the "Yes" column or in the "No" column, the job can be retried safely. If a row lands in the last category, the job belongs on capacity that will not be taken away.

One more constraint worth knowing

Warm pools and interruptible capacity work against each other. A pool of pre-initialized disks exists so that a machine is already waiting when a job arrives, and an instance that can be reclaimed at any moment cannot make that promise. Configurations that keep a warm pool are usually kept separate from the ones running on spot capacity, which the standby disks documentation states directly.

Choosing between interruptible and uninterruptible capacity is a decision you can only make once instance choice belongs to you, which is what the BYOC deployment model describes and what the hosted runners compared with BYOC runners guide walks through for GitHub Actions specifically. The spot instances for GitHub Actions runners guide takes the same subject from job selection through to configuration, and the WarpBuild pricing page lists the per minute rates for each runner type.

FAQ

What is a spot instance?

A spot instance is spare capacity in a cloud provider's fleet, offered at a discount to that provider's standard on-demand rate on the condition that the provider can reclaim the machine when it needs the capacity back. The hardware, the image, and the instance type are the same as an on-demand instance; the difference is tenure, because the provider can end the instance after a short warning.

How much warning does a spot instance give before it is reclaimed?

The warning window is short and provider specific. AWS publishes a two minute Spot Instance interruption notice delivered through instance metadata and EventBridge, Google Cloud publishes a 30 second preemption notice delivered as a shutdown signal, and Azure publishes a 30 second eviction notice through Scheduled Events. All three are best effort signals rather than guarantees, and none of them is long enough to checkpoint a running compile.

Which GitHub Actions jobs are safe to run on spot instances?

Jobs that can be re-run from their first step without cleaning anything up. Unit test shards, lint, type checks, and container builds that push by digest all qualify, because a lost job costs the minutes it had used and nothing else. Release, publish, deploy, and database migration jobs do not qualify, because an interruption halfway through leaves side effects outside the job that a retry then has to reconcile.

What is fallback instance selection and why does it matter on spot?

Fallback instance selection lets a runner configuration name several acceptable instance types instead of one, so the launcher can pick whichever type has capacity at that moment. Spot availability is per pool, meaning per instance type, per zone, so a single pinned type can leave jobs queued while a near identical type nearby has capacity. A longer list of similarly sized types widens the pool the launcher can draw from.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.