Runs and approval
A run is one plan, apply, destroy or drift execution of a stack. Each phase runs in its own Kubernetes Job, and the apply executes exactly what the plan produced.
Run lifecycle
queued ─▶ planning ─┬─▶ no_changes (no changes)
├─▶ planned (type=plan)
├─▶ awaiting_approval ─┬─▶ apply_queued ─▶ applying ─▶ applied
│ └─▶ discarded
├─▶ apply_queued (approval not required)
└─▶ drifted (type=drift)
any running phase ─▶ failed | queued/apply_queued ─▶ canceled
Start a run with its type:
curl -s -H "$H" $O/stacks/$ST/runs -d '{"type": "plan"}'
The types are plan, apply, destroy and drift. See Your first run for how $H, $O and $ST are set.
Queue and lock
Only one run at a time executes or waits for approval on each stack. The others wait in a queue, in order of arrival. The UI shows every active run in the organization on all screens, with runs waiting for approval highlighted.
Apply runs what was planned
The plan records the commit SHA and the plan file, encrypted in the database. The apply checks out that commit and applies that file. It also uses the plan's variables and the config.yaml from that commit. Changing a folder's variables between plan and apply has no effect on this run.
If the state changed after the plan, Terraform itself refuses the saved plan and the run fails, so plan again.
Review the changes
From the plan's structured output, Firstlume lists the changes by resource:
curl -s -H "$H" $O/runs/$RUN/changes | jq .
The run page groups them in this order, with a count per group:
- Will be deleted
- Will be replaced
- Will be updated
- Will be created
Each item shows the address, the type and the attributes that change. A replacement shows which attribute forces it, and the summary counts replacements apart: 1 delete · 1 replace · 2 update · 2 create. Values marked sensitive appear as (sensitive) and never reach the API or the UI. With Terragrunt run --all, each change also names its unit.
The full log stays available in the Logs view, live.
Approval
Approval is an inherited setting, require_approval, and it is required by default. Set it at the organization, on a folder or on a stack, in the UI or through the settings API. The rule is read when the plan finishes.
curl -s -X POST -H "$H" $O/runs/$RUN/approve -d '{"acknowledged": []}'
curl -s -X POST -H "$H" $O/runs/$RUN/discard
Today any member of the organization can approve. Approvers by person or group are planned.
Confirm each deletion or replacement
confirm_destructive is an inherited setting, off by default. When it is on:
- each resource that will be deleted or replaced gets a checkbox in the UI, and the approve button unlocks once all are ticked
- the server checks too: approval is refused with a 409 if any of those addresses is missing from
acknowledged - the confirmed addresses are recorded in the run, with who approved
Follow the apply
During and after the apply, each resource on the list shows its state:
| State | Meaning |
|---|---|
pending |
approved, hasn't started |
applying |
running |
done |
finished |
failed |
failed, with Terraform's error on the resource |
not_applied |
the apply ended before it started, which only happens when another resource failed |
A resource marked not_applied will be in the next plan. A replacement shows its two stages, in the order they ran. The states come from Terraform's machine-readable output (apply -json), while the log stays readable text. An error that belongs to no resource, such as a provider or authentication failure, becomes the run's error.
What each run shows
- Steps: checkout, init, plan, approval, apply and so on, each with status, duration and its own log
- Commit: author, first line of the message and a link to the commit in Git
- Configuration used: the
config.yamlfrom the commit, the effective settings and the variable names, each with its origin. Secrets are hidden - State: which backend each unit used
When runs fail
If the executor stops sending heartbeats for FIRSTLUME_HEARTBEAT_TIMEOUT (10 minutes by default, controlPlane.heartbeatTimeout in Helm), the run is marked failed. If the pod dies, for example from running out of memory or hitting its deadline, the agent reports the failure.
Canceling
Runs that are queued (queued, apply_queued) can be canceled. Canceling a running run isn't supported, on purpose: interrupting an apply can corrupt the state.