backendブロック — Stateのリモート管理とチーム共有

1. 概要

この記事では、以下の内容を解説します。

  • backendブロックの役割(Stateファイルの保存場所を決める)
  • ローカルバックエンド(デフォルト)とリモートバックエンドの違い
  • S3バックエンドの設定方法(DynamoDBによるState Lock含む)
  • バックエンド変更時のterraform initの実行手順
  • チーム開発でのバックエンド設計のポイント
  • よくあるエラーと対処法

backendブロックは、TerraformのStateファイル(terraform.tfstate)をどこに保存するかを定義します。デフォルトではローカルファイルに保存されますが、チーム開発ではS3などのリモートバックエンドを使うことで、複数人が安全にStateを共有できます。


2. backendとは

Terraformは「現在のインフラの状態」をStateファイルに記録します。このStateファイルの保存場所を設定するのがbackendブロックです。

terraform {
  backend "<バックエンドタイプ>" {
    # バックエンド固有の設定
  }
}

ローカルバックエンド(デフォルト)

backendブロックを書かない場合、Stateはローカルのterraform.tfstateファイルに保存されます。

.
├── main.tf
├── terraform.tfstate        ← Stateがここに保存される
└── terraform.tfstate.backup

ローカルバックエンドの問題点:

  • チームで共有できない(個人のPC上にStateがある)
  • GitにStateをコミットすると機密情報(パスワード等)が漏洩するリスク
  • 同時実行の排他制御がない(2人が同時にapplyするとStateが壊れる)

3. S3バックエンドの設定

AWS環境でのリモートバックエンドにはS3 + DynamoDBの組み合わせが定番です。

  • S3: Stateファイルの保存場所
  • DynamoDB: State Lockのテーブル(同時実行防止)

事前準備: S3バケットとDynamoDBテーブルを作成

バックエンド用のリソースは、バックエンドを使う前に手動または別のTerraformプロジェクトで作成する必要があります。

# backend_setup/main.tf(バックエンド用リソースを作る別プロジェクト)
terraform {
  required_version = ">= 1.9"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

# Stateを保存するS3バケット
resource "aws_s3_bucket" "terraform_state" {
  bucket = "myproject-terraform-state"  # バケット名はグローバルで一意

  tags = {
    Name      = "terraform-state"
    ManagedBy = "terraform"
  }
}

# バージョニングを有効化(以前のStateに戻せるようにする)
resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  versioning_configuration {
    status = "Enabled"
  }
}

# Stateファイルの暗号化を有効化
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# パブリックアクセスをブロック(Stateファイルは非公開)
resource "aws_s3_bucket_public_access_block" "terraform_state" {
  bucket                  = aws_s3_bucket.terraform_state.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# State Lockのテーブル(同時実行防止)
resource "aws_dynamodb_table" "terraform_locks" {
  name         = "myproject-terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }

  tags = {
    Name      = "terraform-state-lock"
    ManagedBy = "terraform"
  }
}

backendブロックの設定

S3バックエンドを使うプロジェクトのterraformブロックに記述します。

# main.tf(実際のインフラを管理するプロジェクト)
terraform {
  required_version = ">= 1.9"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {
    bucket         = "myproject-terraform-state"    # S3バケット名
    key            = "prod/terraform.tfstate"        # Stateファイルのパス
    region         = "ap-northeast-1"
    encrypt        = true                            # 保存時の暗号化
    dynamodb_table = "myproject-terraform-locks"     # DynamoDBテーブル名(State Lock)
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

keyの命名規則(複数環境対応)

keyには Stateファイルの S3上のパスを指定します。環境ごとにパスを分けることで、StateファイルをS3上で整理できます。

# 推奨: 環境ごとにkeyを分ける
dev/terraform.tfstate
stg/terraform.tfstate
prd/terraform.tfstate

# モジュールごとに分ける(大規模プロジェクト向け)
prd/vpc/terraform.tfstate
prd/eks/terraform.tfstate
prd/rds/terraform.tfstate

4. terraform initでバックエンドを初期化する

backendブロックを設定したら必ずterraform initを実行します。

# バックエンドを初期化(初回またはバックエンド設定変更後)
terraform init

# 実行例(出力)
Initializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

ローカルからS3バックエンドに移行する

すでにローカルにStateがある場合、-migrate-stateオプションで既存のStateをS3に移行できます。

# 既存のローカルStateをS3バックエンドに移行
terraform init -migrate-state

# 確認プロンプト
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. Would you like 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

5. State Lockの仕組み

dynamodb_tableを設定すると、terraform applyの実行中にDynamoDBにロックレコードが作成され、他の実行をブロックします。

# 別の人がapplyしているときに実行するとロックエラーになる
$ terraform apply

│ Error: Error acquiring the state lock
│
│   Error message: ConditionalCheckFailedException: The conditional request failed
│
│ 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.
│
│ Lock Info:
│   ID:        a1b2c3d4-...
│   Path:      myproject-terraform-state/prod/terraform.tfstate
│   Operation: OperationTypeApply
│   Who:       alice@example.com
│   Version:   1.9.0
│   Created:   2026-01-15 10:30:00 +0000 UTC

正常にapplyが完了するとロックは自動的に解除されます。異常終了でロックが残った場合はterraform force-unlock <LOCK_ID>で解除できます(チームへの確認後に実施すること)。


6. 部分的な変数化(backendの制限)

backendブロックでは変数(var.)・locals・データソースが使えません。 すべての値をハードコードするか、部分的な設定(Partial Configuration)を使います。

# ❌ backend内でvar.は使えない
backend "s3" {
  bucket = var.state_bucket  # Error!
}

# ✅ 対策1: ハードコード(シンプルなプロジェクト向け)
backend "s3" {
  bucket = "myproject-terraform-state"
  key    = "prod/terraform.tfstate"
  region = "ap-northeast-1"
}
# ✅ 対策2: -backend-config オプションで外部から渡す(CI向け)
terraform init \
  -backend-config="bucket=myproject-terraform-state" \
  -backend-config="key=prod/terraform.tfstate" \
  -backend-config="region=ap-northeast-1" \
  -backend-config="dynamodb_table=myproject-terraform-locks"
# ✅ 対策3: backendブロックを空にしてファイルで渡す
# backend.tf
terraform {
  backend "s3" {}  # 中身は空
}
# backend.tfvars
# bucket         = "myproject-terraform-state"
# key            = "prod/terraform.tfstate"
# region         = "ap-northeast-1"
# dynamodb_table = "myproject-terraform-locks"

terraform init -backend-config=backend.tfvars

7. よくあるエラー

Error: Backend configuration changed

│ Error: Backend configuration changed
│
│ A change in the backend configuration has been detected, which may require
│ migrating existing state.

原因: backendブロックの設定(bucket名やkeyなど)を変更したのにterraform initを再実行していない。

解決方法:

terraform init -reconfigure   # Stateは移行せず再設定
terraform init -migrate-state # Stateを新しいバックエンドに移行

Error: Failed to get existing workspaces

│ Error: Failed to get existing workspaces: S3 bucket does not exist.

原因: backendブロックに指定したS3バケットが存在しない、またはIAM権限が不足している。

解決方法: バックエンド用のS3バケットを先に作成し、実行ロールにS3への読み書き権限とDynamoDBへの読み書き権限を付与する。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::myproject-terraform-state",
        "arn:aws:s3:::myproject-terraform-state/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem"],
      "Resource": "arn:aws:dynamodb:ap-northeast-1:*:table/myproject-terraform-locks"
    }
  ]
}

8. 関連記事


9. まとめ

  • backendブロックはTerraform Stateの保存場所を定義する
  • デフォルトはローカル(terraform.tfstate)—チーム開発には向かない
  • AWS環境のリモートバックエンドはS3(保存)+ DynamoDB(State Lock)が定番
  • バックエンド用のS3バケット・DynamoDBテーブルは事前に別途作成しておく
  • S3バックエンドはバージョニング・暗号化・パブリックアクセスブロックを必ず有効化する
  • backendブロックではvar.localsは使えない。-backend-configオプションで外部から渡す
  • backend設定変更後は必ずterraform initを実行すること

動作確認バージョン: Terraform >= 1.9 / AWS Provider ~> 5.0 対象リージョン: ap-northeast-1(東京) 公式ドキュメント: https://developer.hashicorp.com/terraform/language/settings/backends/s3