Terraform

Terraform State Fundamentals: Mastering the Core of State Management

2026-04-19
NicheeLab Editorial Team

Terraform persists the result of every run as state, then uses it as the baseline for subsequent plans and applies. Without proper state design and protection, diffs can break and concurrent runs can collide.

Grounded in the behavior described by the official documentation, this guide focuses on the stable concepts most often tested on the Associate exam and the best practices that apply to both exam prep and real-world operations.

What Is Terraform State?

State is the data Terraform uses to map resources declared in configuration to the actual resources on the cloud. It holds each resource's address, attribute values, dependencies, and outputs, forming the basis for diff calculation on the next plan/apply.

Without state, Terraform would have to analyze the current world from scratch every time, and safe mappings during renames or refactors would be nearly impossible. State is not just a cache: it is the core data that keeps runs consistent.

By default, state is stored locally in terraform.tfstate, but real-world setups typically use a remote backend (S3, AzureRM, Terraform Cloud, etc.) for concurrent execution, backups, and access control.

  • State is essential for diff computation and resource identity tracking
  • Local storage is fine for learning or solo experimentation; teams should use remote backends
  • Editing state by hand is a last resort; normally use the state subcommands

Where state fits during a Terraform run

plan/applyreads/writeslockreads real infraTerraform ConfigProviders (AWS/GCP/..)Backend (State)local/S3/AzureRM/TFCLock storeDynamoDB/Blob/TFC

Choosing a Backend: Local vs Remote

The backend determines where state lives and how it is locked. For team operations, choose a remote backend that prevents concurrent-run collisions and provides history and access control.

The major options break down as follows. S3 provides locking when paired with DynamoDB. AzureRM uses Blob lease for locking. GCS does not support locking, so single-run enforcement is required. Terraform Cloud/Enterprise delivers locking, history, and RBAC in a single integrated package.

  • For team operations, the keys are locking and history
  • S3 enables locking via a DynamoDB table
  • GCS lacks locking, so prevent collisions through CI concurrency control
  • Terraform Cloud delivers locking, history, and permissions with minimal configuration
BackendLockingEncryption (At Rest)Versioning / History
localNoneOS permissions onlyManual backup
S3 (+DynamoDB)Yes (DynamoDB Lock)SSE-S3 / SSE-KMSS3 versioning
AzureRM (Blob)Yes (Blob lease)Storage default encryptionBlob versioning / Soft delete
GCSNoneGoogle-managed keys or CMEKObject Versioning
Terraform Cloud/EnterpriseYesManaged by the serviceState history and Run logs

Example S3 backend with locking enabled

terraform {
  backend "s3" {
    bucket         = "my-tf-state"
    key            = "envs/prod/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "tf-state-lock"
    encrypt        = true
  }
}

Updating State and Operational Commands

During plan and apply, Terraform re-reads the current state of real infrastructure and refreshes state. Any past manual changes (drift) surface as a diff. If you only want to refresh the current view, you can use the refresh-only apply mode.

Avoid editing state directly. Use the terraform state subcommands for address changes (mv) and detachments (rm). Reserve push/pull for special cases such as disaster recovery.

  • Show diffs with plan; apply changes with apply
  • Refresh current state only: terraform apply -refresh-only
  • Use terraform state mv to change resource addresses
  • Detach orphans with terraform state rm (it does not delete the real resource)

Common state operation commands

terraform init
terraform plan

# 状態に登録済みリソースの一覧
terraform state list

# リファクタ時のアドレス変更(計画なしで State を更新)
terraform state mv aws_instance.web[0] aws_instance.app[0]

# 孤児化したエントリの切り離し(実インフラは削除しない)
terraform state rm aws_security_group.legacy

# 現況の再取得のみ(構成は変更せず State を更新)
terraform apply -refresh-only

Locking and Concurrency Control

State locking prevents multiple concurrent runs from corrupting state. The local backend has no locking, which makes it unsuitable for team use. S3 uses a DynamoDB lock record, AzureRM uses Blob lease, and Terraform Cloud handles locking on the service side.

In CI/CD, in addition to backend-level locking, it is effective to serialize runs within the same workspace. This is especially critical for backends without native locking, such as GCS.

  • Local has no locking; avoid it for team use
  • S3 requires a DynamoDB table (LockID partition key)
  • AzureRM uses Blob lease for exclusion
  • Strictly serialize runs per workspace in CI (e.g., one job at a time)

Example DynamoDB lock table creation (for S3 backend)

aws dynamodb create-table \
  --table-name tf-state-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

State File Contents and Security

State contains not only the attributes and dependencies of created resources but can also include outputs and some sensitive data. Even when variables or outputs carry the sensitive flag, the raw values may still live in state, so always treat state itself as sensitive data.

On remote backends, configure encryption (SSE-KMS, default storage encryption, etc.) and strict IAM/RBAC, and enable versioning and delete protection on the bucket/container to guard against accidental loss or corruption. Never commit state to a VCS — that rule is non-negotiable.

  • State can hold sensitive data; minimize read permissions
  • Enable versioning and server-side encryption
  • Do not commit state to Git or other VCS
  • Keep cross-state sharing via remote_state data sources to the strict minimum

Handling sensitive values (the data may still live in state)

variable "db_password" {
  type      = string
  sensitive = true
}

output "db_password" {
  value     = var.db_password
  sensitive = true
}
# 注: sensitive は UI/ログ表示を抑制するフラグ。State から値が完全に排除されるわけではない。

Import and Drift Handling

To place existing resources under Terraform management, you must import them into state. The traditional approach uses the terraform import CLI command to register the resource and then handwrites the matching HCL. Recent versions also support an import block written directly in configuration (availability depends on the Terraform version).

When drift occurs, first review the diff with plan, then either converge with apply (treating the configuration as the source of truth) or restore consistency with the state subcommands as needed. Manual state edits remain a last resort.

  • After import, hand-tune HCL so it matches the source of truth
  • Surface diffs with plan and converge with apply
  • state mv/rm are tools to bring state back in line with configuration
  • Restrict manual edits to recovery scenarios as a last resort

Import examples (CLI and import block)

# 既存の S3 バケットを State に取り込む(従来の CLI)
terraform import aws_s3_bucket.logs my-logs-bucket

# 設定ファイルに import ブロックを記述する方法(対応バージョンのみ)
import {
  id = "my-logs-bucket"
  to = aws_s3_bucket.logs
}

resource "aws_s3_bucket" "logs" {
  bucket = "my-logs-bucket"
}

Check Your Understanding

Associate

問題 1

Which backend configuration most reliably prevents state corruption and concurrent-run collisions in team production operations?

  1. Use the S3 backend and enable locking with a DynamoDB table
  2. Use the GCS backend and enable only bucket versioning
  3. Use the local backend and rely on OS-level file locking
  4. Use no backend at all and only run plan each time

正解: A

The S3 backend, combined with DynamoDB locking, provides mutual exclusion on state. GCS does not support locking, local lacks locking, and running only plan does not prevent collisions.

Frequently Asked Questions

How should I migrate existing state when changing backends?

Use the -migrate-state flag with terraform init to migrate safely. Verify access permissions, encryption, and versioning settings on both the old and new backends beforehand, and confirm there is no diff via plan after migration.

Is it OK to commit terraform.tfstate to Git or another VCS?

No, this is not recommended. State files can contain sensitive data. The best practice is to use a remote backend protected by access control, encryption, and history.

Is separating prod and dev with workspaces safe?

It can work for small setups, but it is inadequate for permission and audit separation. It is safer to split backends or state paths per environment and isolate IAM/RBAC independently.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

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.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.