DEV Community

Zero Heartbeat
Zero Heartbeat

Posted on Originally published at delta1labs.com

Licensing Software That Runs on Ephemeral CI and Cloud Build Agents Without Burning Seats

Licensing models were mostly designed for machines that stay put — a desktop, a server, a workstation. CI and cloud build agents violate that assumption on purpose: they exist for the duration of one job and then vanish, and the next job gets a brand-new VM or container. Point a node-locked license at that environment and it does exactly the wrong thing — every build looks like a new machine, activates, consumes a seat, and then the machine is destroyed without ever releasing it. A ten-seat license can be exhausted before lunch. The fix is not a bigger seat count; it is licensing the dimension that actually varies.

Why ephemeral breaks node-locking

Node-locking derives a fingerprint from a machine's hardware and binds the license to it, so a seat represents a distinct device. That is exactly right for desktops and exactly wrong for ephemeral compute, where "distinct device" is a meaningless unit — there is a new one every few minutes, none of them lasts, and the thing you actually want to limit is not how many machines but how much concurrent (or total) use.

Worse, the cleanup assumption fails too. Node-locked seats are normally freed by an explicit deactivation when a user moves machines. A CI runner is not deactivated; it is deleted. So its seat lingers until something reclaims it, and in a busy pipeline seats are consumed far faster than any manual process frees them.


Ephemeral runners against node-locked vs floating licensing
.b{fill:none;stroke:#64748b;stroke-width:1.5}.t{fill:currentColor;font-size:12.5px}.s{fill:#64748b;font-size:11px}.bad{stroke:#ef4444;stroke-width:1.5}.ok{stroke:#10b981;stroke-width:2}
Ephemeral runners (new fingerprint each run):
run #1
run #2
run #3 …

Node-locked: each run burns a seat,none released → seats exhausted

Floating/concurrency: counts simultaneous use,dead runner's lease auto-reaped → stable
Same workload, two models:

Three models that fit ephemeral compute

Pick by what you actually mean to limit.

Floating / concurrency. License the number of simultaneous uses, not machines. A pool of N concurrent leases means at most N builds run at once; a finished (or destroyed) runner's lease is reclaimed and the next build takes it. This is the natural fit when parallelism is what you are selling — see floating concurrent licenses. Pair it with heartbeat lease reaping so a runner that vanishes without deactivating has its lease freed after a short timeout rather than lingering.

Build-scoped entitlement. If you only need to gate the feature in CI (prove the customer is licensed, without counting machines), validate a signed entitlement with a token that does not consume a seat at all. The build checks the entitlement offline and runs; nothing is node-locked.

Metered builds. If the fair unit is total runs, meter them — count builds as usage against a quota, with an idempotency key so a retried build does not double-count.

Detecting CI and taking the right path

The implementation hinge is letting your software know it is in CI so it takes the ephemeral path instead of the desktop one:

static bool IsCi() =>
    Environment.GetEnvironmentVariable("CI") is "true" or "1"
    || Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true"
    || Environment.GetEnvironmentVariable("GITLAB_CI") == "true"
    || Environment.GetEnvironmentVariable("TF_BUILD") == "true"      // Azure Pipelines
    || Environment.GetEnvironmentVariable("JENKINS_URL") is not null;

// In CI, validate against a concurrency/entitlement path — not a node-locked activation.
var result = IsCi()
    ? keyright.ValidateFloating()   // or a build-scoped entitlement check
    : keyright.Activate();          // normal desktop node-lock
Enter fullscreen mode Exit fullscreen mode

The desktop experience is unchanged; the CI path simply validates the dimension that makes sense there and never writes a per-machine activation that would strand a seat.

Doing this with Keyright

Keyright supports all three models — floating concurrency with heartbeat reaping, build-scoped entitlements validated without consuming a seat, and metered build counting — so you can license ephemeral CI and autoscaled agents by concurrency or usage instead of by machine, and never watch a pipeline exhaust your seats. Each path validates offline against a signed payload, so a runner with restricted egress still works. Keyright is self-hosted, so your entitlements and usage data stay on infrastructure you control.

See the plans, or start from floating concurrent licenses and add CI detection to route runners onto it.

Top comments (0)