Skip to content

Terraform State

Every stack keeps its Terraform state in one of two modes, chosen in the wizard's Git Config step under Terraform State and shown on the stack's Settings tab. The mode cannot be changed after creation.

Mode Who owns the state How it works
Zenfra-managed (default) Zenfra Zenfra stores and locks the state for you. Your source needs no backend block.
Customer-managed S3 You Your source declares an s3 backend and Zenfra never reads, writes or stores the state.

Zenfra-managed

Zenfra keeps state versions for 30 days. Any member can list them through the API; rolling back to an earlier version requires the Admin role. A managed stack cannot be converted to customer-managed, and there is no self-service export of its state.

Customer-managed S3

Choose this when the state must stay in your own account. Your source declares its backend "s3" block; Zenfra verifies that it resolved and never touches the state.

The contract. After init, the worker checks which backend the tool actually resolved and accepts s3 only. No backend block, a local backend, any other remote backend, or unreadable backend metadata fails the run before plan or apply with BACKEND_POLICY_REFUSED and a log line naming the resolved type when there is one, or otherwise why verification failed. Without this refusal a missing backend would write state into the run's temporary workspace and the next run would propose recreating everything.

What you provide.

  • Credentials for the bucket, the same way you provide them for providers: through the stack's AWS integration, which puts credentials in the run environment, or through stack, bundle or run variables. Zenfra does not broker credentials for the backend itself.
  • Locking. State locking is whatever your backend block configures (use_lockfile or a DynamoDB table). Zenfra's own lease still serializes runs of one stack, but it does not lock the remote state.
  • The bucket, key and region, and any endpoint settings for S3-compatible storage.

Backend changes are your migration. The mode is fixed, but the backend block is source code. Every run starts in a clean workspace with a non-interactive init, so Zenfra cannot migrate state between backends. Move the state out of band (for example terraform init -migrate-state from an operator machine) before running the changed source.

Workers. Runs of a customer-managed stack are dispatched only to workers that advertise the external-state-v1 capability. On a private pool, upgrade the worker image; until then such runs stay Queued.

Not available in this mode: state versions and rollback, and the resources inventory.

Reserved variables

This applies to every stack, in both modes. The worker owns the tool's working data directory, home directory and CLI configuration for each run, and refuses the variables that would relocate or bypass them. Setting any of these in a stack, bundle or run variable fails the run with ENV_POLICY_REFUSED and a message naming the variables:

Variable Why
TF_DATA_DIR, HOME, TF_CLI_CONFIG_FILE Worker-owned, set per run
TF_PLUGIN_CACHE_DIR, TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE Would relocate provider binaries outside the run
TF_CLI_ARGS, TF_CLI_ARGS_<subcommand> Would inject arguments into tool commands, including init
TERRAFORM_CONFIG Deprecated alias for TF_CLI_CONFIG_FILE
TF_REATTACH_PROVIDERS Would bypass provider installation

TF_VAR_* and TF_WORKSPACE pass through unchanged. Custom CLI configuration and provider mirrors are not supported while these values are worker-owned. This policy arrived with the September 2026 release; see the upgrade notes if runs started failing with ENV_POLICY_REFUSED.