2026年8月27日

2026年8月27日

Terraformのtfstateファイルの管理方法|リモート保存・ロック・移行・壊れたときの戻し方

Terraformのtfstate(stateファイル)は、「コードに書いたリソースが、実際にはどのIDのものか」を記録した台帳です。これを失ったり、2人が同時に書き換えて壊したりすると、Terraformは既存のリソースを認識できなくなり、二重作成や意図しない削除につながります。この記事では、tfstateに何が入っているかを確認したうえで、S3へのリモート保存とロック、ローカルからの移行、terraform state コマンドの安全な使い方、リソースの名前変更や取り込み、壊れたときの戻し方までを、運用の視点で説明します。

tfstateに何が入っているか

terraform apply を実行すると、作業ディレクトリに terraform.tfstate ができます。中身はJSONで、リソースごとに「Terraform上の名前(例:aws_instance.web)」「クラウド側のID」「属性の値」が記録されています。次回の plan では、この記録とクラウドの実際の状態、そしてコードの3つを突き合わせて差分を出します。

運用上、押さえておくべき性質は3つです。

  1. 秘密情報が平文で入ることがある。 データベースのパスワードや、生成した鍵などの属性値はstateにそのまま記録されます。変数に sensitive = true を付けても、画面表示が伏せられるだけで、stateの中身は暗号化されません。Terraform 1.10以降では、stateに値を残さない仕組み(ephemeralな値や、書き込み専用の引数)が段階的に追加されていますが、対応状況はプロバイダーやリソースによって異なります(2026年10月現在)。stateは「秘密情報を含むファイル」として扱うのが前提です。
  2. 同時に書き換えると壊れる。 2人が同時に apply すると、後から書いた方が先の結果を上書きし、記録と実態がずれます。
  3. 手元だけに置くと失われる。 PCの故障や誤削除でstateを失うと、作ったリソースがTerraformの管理から外れてしまいます。

このため、個人の検証を超えてチームや本番で使う段階では、stateをリモートの保存先(バックエンド)に移し、ロックと履歴を持たせます。

S3にstateを置き、ロックをかける

AWSを使っている場合、S3バケットをバックエンドにする構成が広く使われています。バケット側では次の設定をしておきます。

  • バージョニングを有効にする。 stateが壊れたときに過去の版に戻せるようにするためで、ここが最も大事です。
  • パブリックアクセスをブロックし、暗号化を有効にする。 秘密情報を含むためです。
  • アクセスできるIAMの主体を絞る。 Terraformを実行する人やCIのロールだけが読み書きできるようにします。

バケットそのものは、管理対象とは別の構成(またはコンソール)で先に作っておきます。stateを置くバケットを同じ構成の中で作ろうとすると、「stateの置き場所がまだ無い」という循環が起きるためです。

Terraformの設定は次のとおりです。

terraform {
  backend "s3" {
    bucket       = "your-tfstate-bucket"
    key          = "prod/network/terraform.tfstate"
    region       = "ap-northeast-1"
    encrypt      = true
    use_lockfile = true
  }
}

use_lockfile = true は、S3にロック用のファイル(stateと同じキーに .tflock を付けたもの)を置いて排他制御する方式です。Terraform 1.10で追加され、1.11から正式な機能になりました。それ以前はDynamoDBのテーブルを用意してロックする方式(dynamodb_table)が一般的でしたが、現在は非推奨とされ、将来のバージョンで削除される予定です。古い記事の手順を参考にする場合は、この点に注意してください。

S3を読み書きするIAMの権限としては、バケットに対する s3:ListBucket、stateのキーに対する s3:GetObject と s3:PutObject、ロックファイルの削除に使う s3:DeleteObject が必要です。暗号化にKMSの鍵を使う場合は、その鍵の利用権限も加えます。

key は環境や役割ごとに分けます。1つのstateにすべてを詰め込むと、小さな変更でも全リソースの plan が走って遅くなるうえ、1回の誤操作の影響範囲が広がります。「本番のネットワーク」「本番のアプリ」「ステージング」のように、変更の頻度と影響範囲で分けるのが一般的です。

ローカルのstateをS3に移行する

すでにローカルの terraform.tfstate で運用している場合は、次の手順で移します。

# 1. 念のため現在の state を控える
cp terraform.tfstate terraform.tfstate.before-migrate

# 2. backend "s3" ブロックを追加してから
terraform init -migrate-state

init が既存のstateを検出し、新しいバックエンドにコピーするかを尋ねてくるので yes と答えます。移行後に terraform plan を実行し、「No changes」になることを確かめてください。差分が出る場合は、移行先のキーが違う、別のstateを指している、といった可能性があります。確認できたら、ローカルの terraform.tfstate は誤って使わないよう別の場所に退避します。

バケット名などを環境ごとに切り替えたい場合は、backend ブロックを空に近い形にして、値を別ファイルから渡す方法(部分設定)が使えます。

terraform init -backend-config=backend-prod.hcl

terraform state コマンドを安全に使う

stateの中身を確認・操作するコマンドは次のとおりです。

terraform state list                       # 管理しているリソースの一覧
terraform state show aws_instance.web      # 1件の属性を表示
terraform state pull > backup.tfstate      # リモートの state を手元に保存

確認系の list と show は何度実行しても問題ありません。一方で、state mv(名前の付け替え)、state rm(管理から外す)、state push(stateを上書き)は、stateを直接書き換える操作です。実行前には terraform state pull でバックアップを取り、ほかの人が apply していないことを確認してください。

特に state push は、リモートのstateを手元のファイルで上書きします。版のずれを防ぐ確認を無視する -force を付けると、他の人の変更を消すおそれがあるため、障害対応以外では使わないのが無難です。

リファクタリングと取り込みはコードで書く

かつては state mv や terraform import コマンドで行っていた作業の多くは、現在はコードに書けるようになっています。コードで書くと plan で結果を確認でき、レビューも通せるため、こちらを優先するのがおすすめです。

名前を変える(moved ブロック)。 リソースの名前やモジュールの構成を変えると、Terraformは「古いものを削除し、新しいものを作る」と判断します。moved ブロックで対応付けると、作り直しではなく名前の変更として扱われます。

moved {
  from = aws_instance.web
  to   = aws_instance.app
}

既存のリソースを取り込む(import ブロック)。 手作業で作ったリソースをTerraformの管理下に入れます。Terraform 1.5以降で使えます。

import {
  to = aws_s3_bucket.assets
  id = "your-assets-bucket"
}

terraform plan -generate-config-out=generated.tf を使うと、取り込むリソースの設定のひな形を生成できます。生成されたコードは不要な属性を含むことがあるので、整理してから使います。

管理から外す(removed ブロック)。 リソースは残したまま、Terraformの管理だけをやめたい場合に使います。Terraform 1.7以降で使えます。

removed {
  from = aws_instance.legacy

  lifecycle {
    destroy = false
  }
}

destroy = false を付け忘れると、実際のリソースが削除される計画になります。plan の結果に削除が含まれていないことを確認してから適用してください。

よくあるトラブルと戻し方

ロックが残ったままになる。 apply の途中で通信が切れたり、CIが強制終了したりすると、ロックが解放されずに残ることがあります。エラーにロックIDと、ロックを取得した人・時刻が表示されるので、本当に実行中の処理が無いことを確かめてから解除します。

terraform force-unlock LOCK_ID

実行中の処理があるのに解除すると、同時書き込みでstateを壊す原因になります。

手作業の変更で差分が出る(ドリフト)。 管理画面から直接設定を変えると、次の plan で元に戻す差分が出ます。変更をコードに取り込むか、元に戻すかを決めてから対応します。実際の状態をstateに反映するだけなら、terraform apply -refresh-only で内容を確認しながら更新できます。

stateが壊れた・誤って書き換えた。 S3のバージョニングを有効にしていれば、過去の版のオブジェクトを取り出せます。まず全員の apply を止め、現在の版を state pull で控えてから、正常だった時点の版をダウンロードし、terraform plan で実態と大きくずれていないか確認したうえで戻します。戻した後は、その時点以降に作ったリソースが管理から外れている可能性があるため、plan の結果を丁寧に読んでください。

ワークスペースと環境の分け方

terraform workspace を使うと、同じコードで複数のstateを切り替えられます。S3バックエンドでは、既定ワークスペース以外のstateは env:/ワークスペース名/キー という場所に保存されます。

ただし、ワークスペースは「今どれを選んでいるか」が画面から分かりにくく、本番を誤って操作する事故が起きやすい面があります。本番とステージングでアカウントや権限を分けたい場合は、ディレクトリとstateのキーを環境ごとに分ける構成の方が、取り違えを防ぎやすくなります。

なお、別の構成の出力を読む terraform_remote_state は相手のstate全体を読む権限を要するため、必要な値だけを共有する方法も検討してください。

まとめ

  • tfstateは「コードと実際のリソースの対応表」で、秘密情報が平文で入ることがあります。Gitには入れず、暗号化した保存先で権限を絞って管理します。
  • チームや本番では、バージョニングを有効にしたS3に置き、use_lockfile = true でロックします。DynamoDBでのロックは非推奨になっています。
  • 移行は terraform init -migrate-state、移行後は plan が「No changes」になることを確認します。
  • 名前の変更・取り込み・管理からの除外は、moved・import・removed ブロックで書き、plan で確かめてから適用します。
  • state を直接書き換える操作の前には state pull でバックアップを取り、ロックの強制解除は実行中の処理が無いことを確かめてから行います。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲