Skip to content

Reference

Runner tiers, the Run environment, hosted-state wiring, the Run-state lifecycle, the GitHub events Stacks handles, and the OpenTofu version catalog — the lookup tables for sizing, wiring, and debugging.

The lookup tables behind the guides: what a Run’s environment looks like, how big each tier is, the state-lifecycle a Run moves through, which GitHub events trigger what, and how the OpenTofu catalog works.

Runner tiers

A hosted Run executes in a sandbox sized by the Stack’s runner tier. The tier is a platform-curated envelope — you pick one of three, you don’t dial the numbers. The CPU limit equals the request, so the envelope is predictable.

TiervCPUMemoryEphemeral diskPhase timeout
small (default)11 GiB2 GiB30 min
medium22 GiB5 GiB60 min
large44 GiB10 GiB120 min

The phase timeout is the wall-clock a single plan or apply may run before it is interrupted — size up a Stack whose applies routinely approach it. Metering is tier-minutes, billed per tier as separate quantities; a Stack running on large meters large-minutes. Self-hosted pools have no tier — that compute is yours. See Core concepts for what’s metered and what’s free.

The Run environment

The runner assembles each phase’s environment from three things: automation flags, the hosted-state wiring, and your Stack’s variables.

Automation flags — always set, so tofu never prompts:

VariableValue
TF_IN_AUTOMATION1
TF_INPUT0

Hosted state — the http backend wiring, pointed at the Run’s own short-lived state credential (not one of your CLI state keys):

TF_HTTP_ADDRESS, TF_HTTP_LOCK_ADDRESS, TF_HTTP_UNLOCK_ADDRESS, TF_HTTP_LOCK_METHOD=LOCK, TF_HTTP_UNLOCK_METHOD=UNLOCK, TF_HTTP_USERNAME, TF_HTTP_PASSWORD. A zz_bosun_backend_override.tf file is written into the working directory so the Run always uses hosted state, whatever the config’s own backend block says.

Your variables — each Stack variable is injected by its kind:

Variable kindHow it’s injected
OpenTofu input (tofu)TF_VAR_<name>
Environment (env)<name> — verbatim

Run identity — present only when the Stack has Run identity available:

VariableMeaning
BOSUN_ID_TOKEN_REQUEST_URLGET it with the bearer below to mint a token; pass ?audience=<aud> for the audience your consumer expects
BOSUN_ID_TOKEN_REQUEST_TOKENthe bearer for that request (this Run only)

AWS zero-config federation — set an AWS_PLAN_ROLE_ARN / AWS_APPLY_ROLE_ARN (or a single AWS_ROLE_ARN) Stack variable and the runner mints a token for sts.amazonaws.com, writes it to a file, and wires the AWS SDK to assume the phase’s role — no static keys. See Run identity for the trust-policy shape.

VariableSet by the runner to
AWS_WEB_IDENTITY_TOKEN_FILEthe path of the minted token
AWS_ROLE_ARNthe role for this phase (plan or apply)
AWS_ROLE_SESSION_NAMEbosun-run-<run-id>-<phase> — so CloudTrail attributes every action to one Run

The Run-state lifecycle

A Run is one resource with phases. It moves:

queued → planning → planned
                  ↘ awaiting_approval → applying → applied
        (any phase) ↘ failed | canceled | discarded
StateMeaningYou can
queuedwaiting for a runner slotcancel
planningthe plan phase is runningcancel (safe — plans never write state)
plannedplan finished; terminal for a speculative Run or an empty change plan
awaiting_approvalplan finished with changes; parked for a humanapprove, discard, cancel
applyingthe apply phase is runningcancel only with force
appliedapply succeeded; terminal
faileda phase errored; terminalretry
canceledstopped by a human or superseded; terminalretry
discardedwalked away from at the approval gate; terminal
  • Approve and discard act only on awaiting_approval. An approval binds to that exact plan; if the plan is re-created the approval is void.
  • Cancel works on queued, planning, and awaiting_approval cleanly. Canceling an applying Run needs an explicit force — an interrupted apply can leave state behind the infrastructure (the runner gets a graceful wind-down, but “may be incomplete” is the honest contract).
  • Retry starts a new Run over the failed one’s exact source. Only failed and canceled Runs retry; a discarded Run was a deliberate walk-away.

GitHub events

Stacks reacts to exactly these events; everything else is acknowledged and ignored.

EventAction(s)What happens
Pushto a tracked brancha Change Run under merge_then_apply / auto_apply; nothing under branch_apply (it already applied) or manual
Pull requestopened, synchronize, reopeneda Speculative plan-only Run + a Longbridge Stacks / <stack> check on the touched Stacks
Pull requestclosedcleanup — no Run; on an unmerged close that had applied from its branch, an abandoned-apply warning
Check runrequested_actiona click on a plan check’s Apply button (branch apply)

Ignored: tag pushes, branch/tag deletions, every other pull-request action, and any other event type. Path filtering is prefix-based — a Stack triggers only when a changed file equals or sits under one of its watch paths (which default to the Stack’s own path).

GitHub App permissions

The Longbridge Stacks App requests the minimum it needs:

PermissionLevelFor
ContentsReadcloning your configuration
Pull requestsReadreading a PR’s changed files for path filtering
ChecksRead & writethe plan/apply checks and the Apply button
IssuesRead & writethe post-merge apply comment and the abandoned-apply warning
MetadataReadmandatory for every App

It subscribes to the Push, Pull request, and Check run events. Clones use short-lived per-Run installation tokens — never stored deploy keys.

The OpenTofu version catalog

Stacks runs OpenTofu only — it does not execute BSL-licensed Terraform, hosted or self-hosted.

  • Versions come from a platform-curated catalog: supported OpenTofu minors, each pinned to an exact patch and its upstream release checksum, fetched from official releases and cached. The runner verifies the checksum before it runs the binary — it trusts the catalog, not the mirror.
  • A Stack’s version is chosen from the catalog at creation and defaults to the latest stable then. It never floats: a bump is an explicit, audited Stack edit, because a tofu upgrade can rewrite state format.
  • required_version in your configuration is honored as a fail-fast check, not a resolver — a Run whose pinned version violates it fails early rather than silently picking another.
  • Deprecation policy: a retiring version first warns, then is blocked for new selections, and is never force-upgraded — a Stack pinned to it keeps running until you move it.

The live list of currently supported versions is the catalog the Stack’s create and settings screens offer — pick from there rather than pinning a version from memory.

Hosted-state endpoint

The hosted state backend lives at:

https://api.stacks.lngbrdg.com/state/v1/<org>/<stack>

<stack> is the Stack’s id, so the address you bake into a tofu config survives a Stack rename. It speaks the standard http backend verbs — GET (read the current state), POST (append an immutable version), LOCK / UNLOCK (the backend lock) — over HTTP basic auth, your state key’s id as the username. A contended LOCK answers 423 with the current holder’s identity as the body; see Troubleshooting for reading and clearing it.