「git管理しているtfstateを複数人が同時に変更して、どれが正しいか分からなくなった」
「S3バックエンドを設定しようとしたが、どのファイルに何を書けばいいのか分からない」
こうした悩みはチーム開発でTerraformを導入した直後に必ずといっていいほど発生します。
この記事では、tfstateの仕組みを基礎から解説し、S3バックエンドによるリモート管理の設定手順、DynamoDBを使ったState Lockingの実装、そしてstateが壊れたときのトラブルシューティングまで一気に解説します。
チームでTerraformを安全に運用するための設計知識を、実際のHCLコードと実行例を交えて丁寧に説明します。
この記事のポイント
・tfstateはTerraformがインフラの「現在の状態」を記録するJSONファイル
・S3バックエンド設定でtfstateをチーム共有・バージョン管理できる
・DynamoDB State Lockingで同時実行によるstate破損を防ぐ
・state破損・ロック残留はterraform force-unlockで対処できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
tfstateとは何か──Terraformがインフラを管理する仕組み
Terraformはインフラをコードで定義するツールですが、「今現在のインフラがどういう状態か」を把握するためにtfstateファイル(terraform.tfstate)を使います。tfstateはJSON形式のファイルで、Terraformが管理しているリソース(EC2インスタンス、VPC、S3バケットなど)の現在の状態が記録されています。
# terraform.tfstate の内容(抜粋) { "version": 4, "terraform_version": "1.8.0", "serial": 12, "lineage": "a1b2c3d4-...", "outputs": {}, "resources": [ { "mode": "managed", "type": "aws_instance", "name": "web", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "attributes": { "id": "i-0123456789abcdef0", "ami": "ami-0c02fb55956c7d316", "instance_type": "t3.micro", ... } } ] } ] }
・現在の状態(tfstate)を読み込む
・コードで定義された「あるべき状態」と差分を計算する(Plan)
・差分を埋める変更を実際のインフラに適用する(Apply)
・適用後の新しい状態をtfstateに書き戻す
つまりtfstateは「Terraformの記憶装置」です。tfstateがなければTerraformは現在の状態を把握できず、既存リソースを全て新規作成しようとしてしまいます。また逆に、tfstateに記録されているリソースを手動でAWSコンソール上で削除しても、Terraformはtfstateを見て「そのリソースは存在する」と思い込んだままになります(これを「ドリフト」と呼びます)。
このようにtfstateはTerraformの核心にある重要なファイルであり、その管理方法が運用の品質を大きく左右します。
ローカルtfstateの落とし穴──チーム開発で起きる典型トラブル
Terraformをインストールして `terraform init` を実行すると、デフォルトではtfstateファイルはローカルの作業ディレクトリに作成されます。個人開発や学習環境ではこれで問題ありませんが、チーム開発になった途端に深刻な問題が発生します。問題1:tfstateのコンフリクト
Aさんが手元のtfstateでapplyし、Bさんも別の手元のtfstateでapplyすると、2つのtfstateが食い違ったまま別々のAWS変更が走ります。結果として「誰も把握していないリソース」や「tfstateに記録されているが実際には存在しないリソース」が生まれます。
問題2:同時実行によるstate破損
AさんとBさんがほぼ同時に `terraform apply` を実行すると、tfstateの読み書きが競合し、ファイルが破損したり中途半端な状態で上書きされたりします。復旧に数時間かかるケースも珍しくありません。
問題3:gitにtfstateをコミットする危険性
「とりあえずgitで管理しよう」という発想は理解できますが、tfstateにはAWSのシークレットキーやデータベースのパスワードが平文で含まれることがあります。GitHubのパブリックリポジトリにプッシュしてしまうとセキュリティインシデントに直結します。また、gitのマージコンフリクトでtfstateが壊れることもよくあります。
# .gitignore に必ず追加すること # tfstateはローカル管理の場合でもgitに含めない *.tfstate *.tfstate.* .terraform/ .terraform.lock.hcl # こちらはコミット推奨(プロバイダーバージョン固定のため)
S3バックエンドでtfstateをリモート管理する設定手順
AWSを使う場合、最も一般的なリモートバックエンドはS3です。ここでは実際の設定手順を説明します。1. S3バケットとDynamoDBテーブルをTerraformで作成する
バックエンド自体をコードで管理するには、専用のブートストラップ用ディレクトリを作るのが定石です。# bootstrap/main.tf # このファイルだけはローカルbackendで実行し、S3とDynamoDBを作成する terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "ap-northeast-1" } # tfstate保存用S3バケット resource "aws_s3_bucket" "tfstate" { bucket = "mycompany-terraform-tfstate" lifecycle { prevent_destroy = true # 誤削除防止 } } # バージョニング有効化(tfstateの変更履歴を保持) resource "aws_s3_bucket_versioning" "tfstate" { bucket = aws_s3_bucket.tfstate.id versioning_configuration { status = "Enabled" } } # 暗号化設定(SSE-S3) resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" { bucket = aws_s3_bucket.tfstate.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } # パブリックアクセスを完全ブロック resource "aws_s3_bucket_public_access_block" "tfstate" { bucket = aws_s3_bucket.tfstate.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } # State Locking用DynamoDBテーブル resource "aws_dynamodb_table" "tfstate_lock" { name = "terraform-state-lock" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" attribute { name = "LockID" type = "S" } }
# bootstrapディレクトリで実行 $ cd bootstrap $ terraform init $ terraform apply # 実行結果(抜粋) aws_s3_bucket.tfstate: Creating... aws_s3_bucket.tfstate: Creation complete after 2s [id=mycompany-terraform-tfstate] aws_s3_bucket_versioning.tfstate: Creating... aws_dynamodb_table.tfstate_lock: Creating... aws_s3_bucket_versioning.tfstate: Creation complete after 1s aws_dynamodb_table.tfstate_lock: Creation complete after 5s [id=terraform-state-lock] Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
2. メインプロジェクトにbackend設定を追加する
S3バケットとDynamoDBが作成できたら、メインのTerraformプロジェクトに `backend` ブロックを追加します。# main.tf(または backend.tf として独立させるのが慣習) terraform { required_version = ">= 1.5" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } backend "s3" { bucket = "mycompany-terraform-tfstate" key = "production/terraform.tfstate" # S3内のパス region = "ap-northeast-1" encrypt = true # DynamoDB State Locking dynamodb_table = "terraform-state-lock" } }
key の値は「どの環境・どのモジュールのtfstateか」を識別するS3オブジェクトキーです。複数環境を運用する場合は以下のような命名規則が一般的です。・`production/terraform.tfstate` — 本番環境全体
・`staging/terraform.tfstate` — ステージング環境
・`production/network/terraform.tfstate` — 本番のネットワーク層
・`production/app/terraform.tfstate` — 本番のアプリケーション層
3. terraform initでバックエンドを初期化する
backend設定を追加したら `terraform init` を再実行します。$ terraform init Initializing the backend... Do you want to copy existing state to the new backend? Pre-existing state was found while migrating the previous "local" backend to the newly configured "s3" backend. No existing state was found in the newly configured "s3" backend. Do you want to copy this state to the new "s3" backend? Enter "yes" to copy and "no" to start with an empty state. Enter a value: yes Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... - Reusing previous version of hashicorp/aws from the lock file Terraform has been successfully initialized!
移行後はローカルの `terraform.tfstate` ファイルを削除しても問題ありません(S3に正本があるため)。
Terraformマニュアルを無料で受け取る >>
State Lockingの仕組みとDynamoDBによる同時実行制御
S3バックエンドを設定するだけでは、同時実行によるstate破損を防ぐことができません。これを防ぐのがState Locking(ステートロック)です。1. State Lockingの動作原理
DynamoDBのState Lockingは以下のように動作します。・`terraform apply` 開始時に、DynamoDBの `terraform-state-lock` テーブルにロックレコードを書き込む
・他のユーザーが同時に `terraform apply` を実行しようとすると、ロックレコードを検出してエラーで停止する
・`terraform apply` が正常終了(またはエラー終了)すると、ロックレコードを削除してロックを解放する
# BさんがロックされているときにApplyしようとした場合の出力例 $ terraform apply Acquiring state lock. This may take a few moments... Error: Error acquiring the state lock Error message: ConditionalCheckFailedException: The conditional request failed Lock Info: ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 Path: mycompany-terraform-tfstate/production/terraform.tfstate Operation: OperationTypeApply Who: alice@mycompany.com Version: 1.8.0 Created: 2026-06-27 09:00:00.123456 +0000 UTC Info: Terraform acquires a state lock to protect the state from being written by multiple users at the same time. Please resolve the issue above and try again. For most commands, you can disable locking with the "-lock=false" flag, but this is not recommended.
# AWS CLIでDynamoDBのロック状態を確認する $ aws dynamodb scan \ --table-name terraform-state-lock \ --region ap-northeast-1 { "Items": [ { "LockID": { "S": "mycompany-terraform-tfstate/production/terraform.tfstate" }, "Info": { "S": "{\"ID\":\"a1b2c3d4...\",\"Operation\":\"OperationTypeApply\",\"Who\":\"alice@mycompany.com\",\"Version\":\"1.8.0\",\"Created\":\"2026-06-27T09:00:00.123456Z\"}" } } ], "Count": 1, "ScannedCount": 1 }
2. IAMポリシーでS3とDynamoDBへのアクセスを制限する
チームメンバーが使うIAMユーザーやIAMロールには、tfstateバケットとDynamoDBへの最小権限を付与します。# terraform-backend-policy.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::mycompany-terraform-tfstate", "arn:aws:s3:::mycompany-terraform-tfstate/*" ] }, { "Effect": "Allow", "Action": [ "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem" ], "Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/terraform-state-lock" } ] }
Terraformの基本的なコマンドや変数の扱い方については、Terraform入門|Infrastructure as CodeでAWSインフラをコード化する基礎ハンズオンも合わせて参照してください。
tfstateのトラブルシューティング──破損・ロック残留・ドリフトの対処法
1. ロックが残留してApplyできない場合
`terraform apply` の途中でネットワーク障害やプロセスの強制終了が発生すると、DynamoDBのロックが解放されないまま残ることがあります(ロック残留)。# ロック残留の確認(エラーメッセージのIDを使う) $ terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890 Do you really want to force-unlock? Terraform will remove the lock on the remote state. This will allow local Terraform commands to modify this state, even though it may be still be in use. Only 'yes' will be accepted to confirm. Enter a value: yes Terraform state has been successfully unlocked! The state has been unlocked, and Terraform commands should now be able to run successfully.
2. tfstateが破損した場合の復旧
S3バージョニングを有効にしていれば、破損前のtfstateに戻すことができます。# S3バージョン一覧を確認する $ aws s3api list-object-versions \ --bucket mycompany-terraform-tfstate \ --prefix production/terraform.tfstate \ --region ap-northeast-1 \ --query 'Versions[*].{VersionId:VersionId,LastModified:LastModified,IsLatest:IsLatest}' \ --output table ----------------------------------------------------------------- | ListObjectVersions | +----------+-----------+-------------------------------------+---+ |IsLatest |LastModified |VersionId | +----------+-----------+-------------------------------------+---+ |True |2026-06-27T09:15:00.000Z |K3d8Kf2xNpY.rWabcd... | |False |2026-06-27T09:00:00.000Z |2Mf7Jk9qRsT.vXwxyz... | |False |2026-06-26T18:00:00.000Z |1La6Ij8pQrS.uWvwxy... | +----------+-----------+-------------------------------------+---+ # 特定バージョンのtfstateをダウンロードして確認する $ aws s3api get-object \ --bucket mycompany-terraform-tfstate \ --key production/terraform.tfstate \ --version-id 2Mf7Jk9qRsT.vXwxyz... \ --region ap-northeast-1 \ terraform.tfstate.backup # 問題がなければそのバージョンを最新に戻す(上書きアップロード) $ aws s3 cp terraform.tfstate.backup \ s3://mycompany-terraform-tfstate/production/terraform.tfstate \ --region ap-northeast-1
3. ドリフト(実態とtfstateの乖離)を検出・解消する
AWSコンソールで手動変更を行った場合、tfstateと実際のインフラの状態がずれます(ドリフト)。# ドリフトの検出(-refresh-onlyオプションで安全に確認) $ terraform plan -refresh-only aws_instance.web: Refreshing state... [id=i-0123456789abcdef0] Note: Objects have changed outside of Terraform Terraform detected the following changes made outside of Terraform since the last "terraform apply": # aws_instance.web has been changed ~ resource "aws_instance" "web" { id = "i-0123456789abcdef0" ~ instance_type = "t3.micro" -> "t3.small" # コンソールで変更された ... } This is a refresh-only plan, so Terraform will not take any actions to undo these. If you were expecting these changes then you can apply this plan to record the updated values in the Terraform state without changing any remote objects.
・tfstateをリアルの状態に合わせる:`terraform apply -refresh-only` でtfstateだけを更新する
・リアルのインフラをコードの状態に戻す:`terraform apply` でインフラ側を元に戻す
どちらが正しいかはチームの判断ですが、「手動変更は原則禁止、すべてをTerraformで管理する」というルールを徹底することが根本的な解決策です。
Terraformで作成するリソースの詳細な設定(VPC、EC2など)については、TerraformでAWS VPCとEC2を構築する方法も参考にしてください。
チーム運用のベストプラクティス──workspaceとCI/CDとの組み合わせ
1. Terraform workspaceで環境を分離する
`terraform workspace` を使うと、同じ設定ファイルから複数の環境(dev/staging/production)のtfstateを分離して管理できます。# ワークスペースの作成と切り替え $ terraform workspace new dev Created and switched to workspace "dev"! $ terraform workspace new staging Created and switched to workspace "staging"! $ terraform workspace list default dev * staging ← 現在のワークスペース $ terraform workspace select production Switched to workspace "production". # workspace名をtfstate pathに自動反映(S3バックエンドの場合) # デフォルトでは env:/
/production/terraform.tfstate に保存される
2. GitHub ActionsからTerraformを実行する
CI/CDパイプラインからTerraformを実行することで、「手元で実行する」という手動運用を排除できます。# .github/workflows/terraform.yml name: Terraform on: push: branches: [main] pull_request: branches: [main] permissions: contents: read id-token: write # OIDC認証用 jobs: terraform: runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 # OIDC経由でAWSに認証(アクセスキーを使わない安全な方法) - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: ap-northeast-1 - uses: hashicorp/setup-terraform@v3 with: terraform_version: "1.8.0" - name: Terraform Init run: terraform init - name: Terraform Format Check run: terraform fmt -check - name: Terraform Validate run: terraform validate # PRの場合はPlanのみ(Applyはmainマージ後のみ) - name: Terraform Plan if: github.event_name == 'pull_request' run: terraform plan -no-color continue-on-error: true - name: Terraform Apply if: github.ref == 'refs/heads/main' && github.event_name == 'push' run: terraform apply -auto-approve -no-color
・PRでterraform planの結果をレビューできる
・mainブランチにマージされた変更だけがapplyされる
・インフラ変更の履歴がgitのコミット履歴として残る
・手元のAWS認証情報を各エンジニアが持たなくてよい(OIDC認証)
3. チーム運用のルール整備チェックリスト
| チェック項目 | 推奨設定 |
|---|---|
| tfstateのバックエンド | S3バケット(バージョニング有効・暗号化有効) |
| 同時実行制御 | DynamoDB State Lockingを設定する |
| tfstateのgit管理 | .gitignoreに*.tfstateを追加して除外する |
| Apply実行者 | CI/CDパイプライン(GitHub Actions等)に限定する |
| AWSアクセス方法 | IAMユーザーのアクセスキーではなくOIDCを使う |
| 環境分離 | workspaceまたはディレクトリ分割でdev/staging/prodを分ける |
| 手動変更の禁止 | AWSコンソールでの直接変更をチームルールで禁止する |
| tfstate破損時の復旧 | S3バージョニングで過去のtfstateに戻せる体制を確認する |
本記事のまとめ
tfstateとS3バックエンドについて、基礎から実践まで解説しました。| やりたいこと | 設定・コマンド |
|---|---|
| S3バックエンドを設定する | terraform { backend "s3" { ... } } |
| バックエンドを初期化・移行する | terraform init |
| ロック残留を解除する | terraform force-unlock <LOCK_ID> |
| ドリフトを検出する | terraform plan -refresh-only |
| tfstateをリアルに合わせる | terraform apply -refresh-only |
| ワークスペースを切り替える | terraform workspace select <NAME> |
| S3の過去バージョンを確認する | aws s3api list-object-versions |
tfstateの設計はTerraformプロジェクトの基盤です。最初にしっかり設計しておくことで、その後の運用が大幅に楽になります。
Terraformマニュアルを無料で受け取る >>
・Terraform入門|Infrastructure as CodeでAWSインフラをコード化する基礎ハンズオン
・TerraformでAWS VPCとEC2を構築する方法|HCL記法とterraformコマンドの実践
・Linux ポート確認の全コマンド|ss・lsofの使い方
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:Terraformのmoduleとfor_each・countで構成を再利用する設計パターン
- 前のページへ:TerraformでAWS VPCとEC2を構築する方法|HCL記法とterraformコマンドの実践
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます