Terraform treats the state file (tfstate) as the single source of truth, so collisions between concurrent runs can corrupt or destroy it. State Locking is the official mechanism for preventing this — on supported backends, mutual exclusion is enforced automatically.
This article covers backend-by-backend lock behavior, how to implement S3+DynamoDB locking, how to handle collisions, CI/CD serialization patterns, and the points most often tested on the Associate/Pro exams.
On plan, apply, destroy, and similar runs, Terraform acquires a lock on the state file whenever the backend supports it. This prevents another process from updating the same state at the same time. If the lock cannot be acquired, the CLI waits (adjustable with -lock-timeout) or eventually fails on timeout.
If the backend does not implement locking, concurrent runs fall into a dangerous "last writer wins" mode, leading to state inconsistencies and orphaned ("ghost") resources. For team or CI/CD use, the basic rule is to always choose a remote backend that supports locking.
Where lock information is stored depends on the backend: DynamoDB for S3, a Blob Lease for AzureRM, the workspace run queue for Terraform Cloud, and so on. Some backends write a .terraform.tfstate.lock.info file in the working directory, which is useful for diagnosing collisions.
| Backend | Locking Support | Lock Implementation / Requirements | Notes |
|---|---|---|---|
| local | No | None | Recommended only for personal or evaluation use |
| s3 | Yes (DynamoDB required) | DynamoDB table with partition key = LockID | S3 versioning, encryption, and KMS are strongly recommended |
| azurerm | Yes | Blob Storage Lease | No additional external store required |
| gcs | Yes | Uses Cloud Storage generation control and conditional writes (managed by Terraform) | No external store required |
| consul | Yes | Consul session lock | Requires a Consul cluster |
| http | No | None | You must implement collision avoidance yourself |
Concurrent CI/CD runs and the lock flow (S3 + DynamoDB example)
Example of specifying a lock wait time (for collision-prone environments)
terraform apply -lock-timeout=5mLocking support is a decisive factor when choosing a backend. For team and CI/CD use, avoid lock-less options like local and http, and pick s3+dynamodb, azurerm, gcs, or remote (Terraform Cloud) according to your operational requirements.
S3 only achieves real locking when combined with a DynamoDB table. Azure Blob enforces mutual exclusion via Lease, GCS prevents collisions through generation control and conditional writes, and the Terraform Cloud remote backend serializes workspace runs without any additional external store.
Configuration examples for representative backends (excerpt)
# S3 + DynamoDB
terraform {
backend "s3" {
bucket = "my-tfstate-bucket"
key = "env/prod/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "tf-locks"
encrypt = true
}
}
# AzureRM (Blob Leaseによるロック)
terraform {
backend "azurerm" {
resource_group_name = "rg-tfstate"
storage_account_name = "sttfstate001"
container_name = "tfstate"
key = "env/prod/terraform.tfstate"
}
}
# GCS(世代管理+条件付き書き込み)
terraform {
backend "gcs" {
bucket = "tfstate-bucket"
prefix = "env/prod"
}
}
# Terraform Cloud(実行キューによる直列化)
terraform {
backend "remote" {
organization = "my-org"
workspaces {
name = "infra-prod"
}
}
}The most widely adopted setup is using S3 as the state store and DynamoDB for locking. This keeps things safe even with multiple runners or multiple developers running Terraform concurrently.
In production, enable S3 versioning, KMS encryption, and a strict bucket policy, and provision a simple DynamoDB table with partition key = LockID (String).
Required AWS resource definitions (example)
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "tfstate" {
bucket = "my-tfstate-bucket"
}
resource "aws_s3_bucket_versioning" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
versioning_configuration { status = "Enabled" }
}
resource "aws_kms_key" "tfstate" {
description = "KMS for Terraform state"
deletion_window_in_days = 30
}
resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
rule { apply_server_side_encryption_by_default { kms_master_key_id = aws_kms_key.tfstate.arn, sse_algorithm = "aws:kms" } }
}
resource "aws_dynamodb_table" "locks" {
name = "tf-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute { name = "LockID" type = "S" }
}
# バックエンド設定(別ファイルやinit時の-varフラグで渡す)
# terraform {
# backend "s3" {
# bucket = "my-tfstate-bucket"
# key = "env/prod/terraform.tfstate"
# region = "ap-northeast-1"
# dynamodb_table = "tf-locks"
# encrypt = true
# }
# }On a lock collision, Terraform prints messages like "Error acquiring the state lock" and shows information about the lock holder (ID, Operation, Who, Created, Path, etc.). Waiting long enough via -lock-timeout resolves most cases.
If a process crash leaves an orphaned lock behind, clear it manually with terraform force-unlock <ID>. Before unlocking, always confirm that no other apply is actually running.
Example error message and unlock command
# 典型的なエラー例(抜粋)
# Error: Error acquiring the state lock
# Details: Resource temporarily unavailable
# Lock Info:
# ID: 9a6e5e1f-4f0a-4c2a-9a9d-cb1b0e5d1234
# Operation: OperationTypeApply
# Who: runner@build-host
# Created: 2026-04-18 09:15:22
# Info: Working Directory: ., State: s3/env/prod/terraform.tfstate
# 手動解除(孤立ロックのみ)
terraform force-unlock 9a6e5e1f-4f0a-4c2a-9a9d-cb1b0e5d1234Locking prevents simultaneous state updates, but pipeline design also needs to ensure "the same workspace is never touched concurrently." Separate state per environment (via workspaces or directory splits) and serialize jobs that target the same environment.
With the Terraform Cloud remote backend, runs are automatically serialized per workspace, and you also get queue management, RBAC, and policy guardrails. For self-hosted setups, layer backend locking together with your CI tool's concurrency feature.
Serialization example with GitHub Actions + Terraform Cloud backend
# .github/workflows/terraform.yml(同一環境を直列化)
name: terraform
on: [push]
concurrency:
group: tf-prod # 同一グループは直列化
cancel-in-progress: false
jobs:
apply:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init -input=false
- run: terraform apply -auto-approve -lock-timeout=10m
# Terraform Cloudのremoteバックエンド
terraform {
backend "remote" {
organization = "my-org"
workspaces { name = "infra-prod" }
}
}Associate/Pro exams frequently test the basics: DynamoDB is required to enable locking on S3, AzureRM uses Blob Lease, remote relies on queue serialization, and local/http have no locking. They also commonly test that -parallelism is not locking and that force-unlock is a last resort.
Even a plan acquires a lock, so be mindful of concurrent reads against the same state. In CI, raise the safety bar with both environment separation and serialization.
Quick reference for options (frequently tested on Associate/Pro)
# ロック関連
terraform plan -lock-timeout=5m
terraform apply -lock-timeout=10m
terraform force-unlock <LOCK_ID> # 孤立ロックの解除(慎重に)
# 並列度(ロックとは別概念)
terraform apply -parallelism=5Associate / Pro
問題 1
Multiple CI runners reference state on the same S3 backend, and applies occasionally collide and fail. Which is the recommended fix that involves the smallest change?
正解: A
Proper locking on the S3 backend is only enabled when you pair it with a DynamoDB table. Versioning improves recoverability but is not a lock. -parallelism is resource-level parallelism and does not prevent collisions. Sharing a single state file across multiple workspaces is also incorrect.
Does terraform plan also acquire a lock?
Yes. Operations that read, write, or update state — plan, refresh, apply, destroy, import — all acquire a lock when the backend supports locking. Pure read-only operations such as show or simple state inspection may not acquire a lock.
Is it safe to use -lock=false?
Not recommended. Reserve it for emergency recovery scenarios where you can strictly guarantee no other process is running concurrently. Using it routinely in normal operations or CI/CD carries a high risk of state corruption.
What is the correct procedure for clearing an orphaned lock?
First, confirm that no other Terraform run is actually active — contact teammates and review run logs if needed. Then run terraform force-unlock <ID> using the lock ID from the error message. Record the unlock action in an audit log and proceed cautiously with the next apply.
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...