Terraform state is the heart of IaC operations. Normally apply/plan and the backend handle it safely, but during incident response or audits you may need direct low-level operations. This article centers on terraform state pull/push and organizes the procedures and precautions for avoiding destructive changes.
We follow HashiCorp's official baseline specifications and add notes for version-dependent behavior. From an exam-prep perspective (Pro/Advanced), we also cover when to choose each command, locking, and backend differences.
terraform state pull fetches the current state from the backend and prints it as JSON on standard output. It is read-only and does not modify infrastructure or the backend. In contrast, terraform state push writes a local tfstate file back to the backend. It depends heavily on backend and lock constraints, and misuse can cause destructive diffs and conflicts.
State carries a serial (generation number) and a lineage (lineage ID), and only the correct successor generation is accepted. A push is typically rejected on serial mismatch, but -force can override this. Forced overwrite is a last resort: stop concurrent runs, acquire a lock, take a backup immediately before, and run a plan verification immediately after.
| Command / Operation | Primary Purpose | Typical Use Case | Mutating? |
|---|---|---|---|
| terraform state pull | Fetch state (audit / backup) | Take a backup, inspect JSON, audit | No (read-only) |
| terraform state push | Restore / replace state | Disaster recovery, rollback after accidental deletion (last resort) | Yes (state rewrite) |
| terraform state mv/rm | Move / remove state entries | Module reorg, fixing a mistaken import | Yes (state edit) |
| terraform import | Import an existing real resource | Bring manually-created resources under IaC | Yes (state addition) |
| apply -refresh-only | Sync state with the live environment | Drift correction (state-only update) | Yes (state update) |
Conceptual diagram: pull/push data flow and locking
Inspecting and comparing state metadata
# Fetch the current state and check metadata
terraform state pull > current.tfstate
jq '{serial, lineage, resources: (.resources|length)}' current.tfstate
# Lightweight comparison against a recent backup (count/meta)
jq '{serial, lineage}' current.tfstate backup-20240115.tfstate
diff <(jq -S '.resources[].type+"."+.resources[].name? // empty' current.tfstate | sort) \
<(jq -S '.resources[].type+"."+.resources[].name? // empty' backup-20240115.tfstate | sort)pull is useful as a starting point for investigation, auditing, and backup. It is generally safe when no plan/apply is in progress. The resulting JSON may contain sensitive data, so pay attention to the output destination and access controls.
On managed backends, organization/workspace permissions may restrict fetching. Verify authentication (terraform login, etc.) and permissions before running, and restrict long-term storage to encrypted locations.
| Check Item | Recommendation | Notes |
|---|---|---|
| Concurrent execution | Confirm none | Ensure via CI/CD pause or manual lock |
| Output destination | Encrypted storage | SSE-enabled bucket or KMS-encrypted disk |
| Inspection method | Summarize with jq | Catch anomalies early from serial / lineage / resource counts |
Pull example and summarization
# Take a backup (with timestamp)
ts=$(date +%Y%m%d-%H%M%S)
terraform state pull > backup-$ts.tfstate
# Confirm metadata health
jq '{serial, lineage, resCount: (.resources|length)}' backup-$ts.tfstate
# Simple scan for sensitive content (do not over-rely on this)
egrep -in '(password|secret|token|apikey)' backup-$ts.tfstate || truepush replaces the state. Misuse triggers large diffs and destructive changes. Do not use it during normal operations; reserve it as a last resort such as disaster recovery or rollback after a mistake. Support availability and lock prerequisites depend on the backend.
Perform safe recovery in this order: stop concurrent runs → take a backup → validate → acquire a lock (if possible) → push → double-verify with a plan immediately after. If rejected due to serial mismatch, avoid forced overwrite without investigating the cause.
| Step | Purpose | Response on Failure |
|---|---|---|
| Stop / Announce | Prevent conflicts | Investigate root cause while keeping the freeze in place |
| Backup | Rollback insurance | Store redundantly in a separate location |
| Compare / Validate | Confirm validity of the recovery candidate | Get to the root cause of serial / lineage / count differences |
| Acquire lock | Exclusivity for writes | If unavailable, change the time window or strengthen the operations freeze |
| push / Validate | Apply and verify the diff | Decide on immediate recovery or rollback |
Push procedure (example: S3 backend)
# 1) Save the current state
terraform state pull > before-$ts.tfstate
# 2) Confirm metadata of the recovery candidate (backup-20240115.tfstate)
jq '{serial, lineage}' backup-20240115.tfstate
# 3) If possible, perform during a window where the lock is available
# (monitor the lock via the S3+DynamoDB table)
# 4) Try the push (initially without -force)
terraform state push backup-20240115.tfstate
# Investigate the reason if rejected for serial mismatch, etc. Only when truly
# necessary, do a forced overwrite under an operations freeze.
# terraform state push -force backup-20240115.tfstate
# 5) Verify the diff immediately afterward
terraform plan -refresh=false # First, check only the code/state alignment
terraform plan # Then verify the diff including the live envBackends differ in lock mechanisms and API constraints. Confirm whether push/pull is allowed and its prerequisites in each backend's documentation. In particular, managed backends strictly control generations and permissions server-side, and push may be unsupported or restricted.
For backend migration, prefer terraform init -migrate-state over manual transfer with push. For HTTP backends, the server implementation determines what is supported, so it is safer to avoid push in environments without a defined spec.
| Backend | Lock Mechanism (example) | push/pull Notes (general) |
|---|---|---|
| S3(+DynamoDB) | DynamoDB item | pull is generally allowed. push requires a freeze, a lock, and root-cause analysis on failure |
| GCS | Conditional write | pull is allowed. Run push only with concurrent-write prevention in place |
| Azure Blob | Blob Lease | pull is allowed. Perform push under a lease |
| HTTP | Depends on server implementation | Not possible if the server does not support it |
| Terraform Cloud/Enterprise | Service-managed | Permission/feature restrictions apply. Confirm with the official API/UI |
Representative backend configuration (HCL excerpt)
# S3 backend (example)
terraform {
backend "s3" {
bucket = "tfstate-prod"
key = "network/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "tfstate-lock"
encrypt = true
}
}
# GCS backend (example)
terraform {
backend "gcs" {
bucket = "tfstate-prod-gcs"
prefix = "network"
}
}State contains a wide range of resource attributes, stored in plaintext unless the provider hides them via sensitive attributes. Even if the backend encrypts at rest, the local file produced by pull is plaintext JSON. Establish governance around access control and storage.
Begin audits with the minimum required metadata and a count summary; do not distribute the full file. Restrict long-term storage to systems with key management (KMS, etc.) and audit trails. Pasting into shared channels (email/chat) is strictly forbidden.
| Target | Recommended Control | Notes |
|---|---|---|
| Backend | Encryption, versioning, locking | Link to audit logs and alerts |
| Local output | Encrypted disk, least privilege | Auto-delete policy |
| Distribution | Summaries only; no sharing | Approval-based workflow |
Safe audit summary sample
# Extract resource count and a list of types
jq '{serial, lineage, count: (.resources|length), types: ([.resources[].type] | unique)}' backup.tfstate
# Search for explicitly sensitive candidate attributes
jq '.. | objects | to_entries[] | select(.key|test("(?i)(password|secret|token|apikey)"))' backup.tfstateQuestions tend to focus on which command is appropriate and what the safe order of operations is. pull/push are low-level operations; ordinarily mv/rm/import, init -migrate-state, or apply -refresh-only achieves the goal. Remember push as a last resort for disaster recovery.
It is also important to correctly articulate the relationship between state and real resources. Rewriting state does not change real resources. Conversely, importing without HCL code does not bring resources under management. Avoid mixing these concepts.
| Goal | Likely-Correct Operation | Wrong-Answer Pattern |
|---|---|---|
| Backend migration | init -migrate-state | Manually transferring with state push |
| Drift investigation | pull + plan | Overwriting via push |
| Module reorganization | state mv | import or manual re-creation |
How to distinguish common options (example)
# Bring an existing VPC under IaC management
# Wrong: replace the VPC state with state push
# Right: terraform import aws_vpc.main vpc-xxxx && plan
# Provider naming change
# Right: terraform state replace-provider 'hashicorp/aws' 'hashicorp/aws' # example of an address fixPro
問題 1
You are running an S3 backend (with DynamoDB for locking). Due to a mistake, you need to roll the state back to an older backup. Which is the safest procedure?
正解: A
The safe procedure is freeze, save, validate, lock, push, and verify with plan immediately after. Overwriting S3 directly bypasses generation validation and locking, which is dangerous. Overusing refresh or starting from -force is also inappropriate. Hand-editing via a backend switch can break consistency; for migrations use init -migrate-state.
Does terraform state pull acquire a lock?
Generally, pull is a read operation and does not acquire a write lock. However, if a run is in progress you may capture an inconsistent snapshot, so it is recommended not to overlap pull with plan/apply runs.
Is terraform state push supported on every backend?
No. It depends on the backend implementation. Managed backends and others may not support it or may impose restrictions. Check the official documentation for each backend to confirm availability and prerequisites.
What should you verify first after a push?
Run terraform plan immediately afterward and verify there are no unexpected destroy/add operations. It is safer to first run with -refresh=false to check code/state alignment, then run a regular plan to also verify the diff against the live environment.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...