Find the state version an apply left
Optional: hand this page to your coding agentThe steps work by hand too.Show the whole prompt
Read https://intentius.io/terragucci/guides/find-a-state-version/.
Find the buckets and containers this repo's roots keep their state in, check whether each keeps versions, and write the commands that would turn it on as a file for me to review. Open a pull request.
Do not change a bucket, a state file or a lock.
Never apply, approve (a pull request review or `terragucci approve`), override a policy denial (`terragucci override`), use `--mode apply`, or merge; never touch `.chant/allowed_signers` or `chant/lifecycle`.Result
Section titled “Result”After each tf-apply wave, the version id of each root’s state, next to the run that wrote it. When a state goes wrong you know which version to restore, and which commit and wave wrote the one after it.
| Where | What it holds |
|---|---|
| the wave’s log | one line per root: where its state is and its version id |
the wave’s report.json |
roots[].state, for each root that applied or had nothing to apply |
the project’s states.json in the reports bucket |
each root’s newest 20 versions, each with its commit, wave and report |
| the estate page | a State versions section, per project and root, newest first |
| the audit trail | detail.state_versions on the wave’s apply entry |
terragucci reads only the state object’s metadata; it never copies or stores a state’s contents. GitLab answers a state’s serial only with the state itself, so on GitLab-managed state terragucci reads the state and keeps only the serial.
Prerequisites
Section titled “Prerequisites”| You need | Why |
|---|---|
Roots with an s3, gcs or azurerm backend that keeps versions, or on GitLab-managed state |
the version id is the store’s own (see below), or GitLab’s serial |
| Reports in a bucket | states.json and the estate page live there |
| The estate page, to see the list | the page reads states.json |
A Terragrunt unit is listed as a root is. Its working directory is in Terragrunt’s cache, so terragucci reads its backend from its evaluated remote_state block (terragrunt render --json).
Each backend names a version its own way:
| Backend | Version id | Kept when |
|---|---|---|
s3 |
x-amz-version-id |
bucket versioning is on |
gcs |
the object’s generation | object versioning is on |
azurerm |
x-ms-version-id |
blob versioning is on |
azurerm without blob versioning |
the time of a snapshot terragucci takes of the state blob after the apply | the backend sets snapshot = true |
Other roots are listed with what terragucci knows:
| Root | Listed as |
|---|---|
| a bucket or account that keeps no versions | versions are off: the store keeps only the latest state |
a local, pg, kubernetes or consul backend, or an http backend other than GitLab’s |
versions are off: each write replaces the state |
a backend that keeps versions terragucci does not read (remote) |
versions not read, naming why |
a Terragrunt unit with no remote_state block that runs from Terragrunt’s cache |
versions not read, naming why |
In report.json and states.json, versioning is off for the first two rows and unknown for the last two.
-
Turn on versioning where your state is:
Terminal window aws s3api put-bucket-versioning --bucket acme-state --versioning-configuration Status=Enabledgcloud storage buckets update gs://acme-state --versioningaz storage account blob-service-properties update --account-name acmestate --enable-versioning trueThe store then keeps each version of every state object until a lifecycle rule expires it. A rule that expires noncurrent versions after 90 days keeps the list short and the bill small.
-
Check the apply job can read the state object’s metadata, which the apply identity already can for the backend. terragucci uses the credentials the backend’s configuration names (
access_keyandsecret_keyfor S3;access_token,credentialsorimpersonate_service_accountfor GCS;access_keyorsas_tokenfor Azure), else the job’s own. A role an S3 backend assumes itself (assume_role) is not assumed; if the job’s own identity may not read the object, the root is listed with versions unknown and the reason. Reading whether a GCS bucket keeps versions needsstorage.buckets.get; without it the generation is recorded as kept. -
Let a wave apply. Each root that applied, or had nothing to apply, gets a line in the log:
envs/dev/platform: state s3://acme-state/envs/dev/platform.tfstate version 3HL4kqtJlcpXroDTDmJ.rmSpXd3dIbrHYA store that keeps no versions prints
versions offand why. A version that cannot be read never fails the wave. -
Open the estate page. Run
terragucci estate(or let its scheduled job run) and open the link it prints. The State versions section lists each root’s versions, newest first, each linked to the report of the wave that wrote it; a root whose bucket keeps no versions says versions are off.
GitLab-managed state
Section titled “GitLab-managed state”A root on GitLab-managed state is an http backend whose address is a project’s state API, <api>/projects/<id>/terraform/state/<name>, in the block or TF_HTTP_ADDRESS. The apply job reads the state with the backend’s own credentials, TF_HTTP_USERNAME and TF_HTTP_PASSWORD (GitLab-managed state), and logs its serial:
envs/dev/platform: state https://gitlab.example.com/api/v4/projects/42/terraform/state/dev-platform version 7GitLab keeps each version by serial, so no setting turns versions on.
Go back to a version
Section titled “Go back to a version”Restoring a state version is a state write outside the gate, so take it through review like any other change. With the version id from the page:
aws s3api get-object --bucket acme-state --key envs/dev/platform.tfstate \
--version-id 3HL4kqtJlcpXroDTDmJ.rmSpXd3dIbrHY restore.tfstate
tofu -chdir=envs/dev/platform state push -force restore.tfstate
tofu -chdir=envs/dev/platform planOn GCS the version is a generation, and on Azure a version id or a snapshot. Fetch it, then push it as above:
gcloud storage cp "gs://acme-state/envs/dev/platform/default.tfstate#1791612469077717" restore.tfstate
az storage blob download --account-name acmestate --container-name tfstate --name platform.tfstate \
--snapshot 2026-10-10T06:09:35.6000000Z --file restore.tfstateOn GitLab-managed state, download the version by its serial with a token that can read the project’s state:
curl --fail --user "$TF_HTTP_USERNAME:$TF_HTTP_PASSWORD" -o restore.tfstate \
https://gitlab.example.com/api/v4/projects/42/terraform/state/dev-platform/versions/7state push takes the backend’s lock while it writes. The older state lacks resources created after it, so the plan shows them as creates. Applying it would make a second copy of each. Import them, or remove them from the cloud, before the next wave applies the root.
- Report JSON schema describes
roots[].stateandstates.json. - The reports bucket lists where
states.jsonlives and who can read it.
These docs count page views and clicks with PostHog. They set no cookies, store nothing in your browser, and send nothing when your browser asks not to be tracked.