SJ blog
devops
A

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

Terraformベストプラクティス2026

Terraform を本番で運用するためのベストプラクティスを解説。state 管理・モジュール設計・drift 検出・セキュリティスキャン・CI/CD 統合まで、現場で使える実践知識をまとめます。

一言結論

TerraformをチームでSafely運用するにはS3+DynamoDBによるリモートstate管理と環境別ディレクトリ構成が土台であり、CI/CDでのplan結果レビューとtfsecによるセキュリティスキャンを組み込むことで初めて本番grade のIaCになる。

1. State をリモートに保存する

ローカルの .tfstate は共同作業では即座に問題になります。

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-lock"  # 同時実行ロック
  }
}

2. ディレクトリ構成

terraform/
├── modules/            # 再利用可能なモジュール
│   ├── vpc/
│   ├── eks/
│   └── rds/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   └── prod/
└── _global/            # 全環境共通(IAM等)

3. モジュールで再利用する

# modules/rds/main.tf
resource "aws_db_instance" "main" {
  identifier        = var.identifier
  engine            = "postgres"
  engine_version    = var.engine_version
  instance_class    = var.instance_class
  allocated_storage = var.allocated_storage
  multi_az          = var.multi_az

  backup_retention_period = 7
  deletion_protection     = var.deletion_protection
  skip_final_snapshot     = !var.deletion_protection
}
# environments/prod/main.tf
module "database" {
  source = "../../modules/rds"

  identifier          = "prod-db"
  instance_class      = "db.t4g.medium"
  multi_az            = true
  deletion_protection = true
}

4. 変数と tfvars の管理

# variables.tf
variable "instance_type" {
  type        = string
  description = "EC2インスタンスタイプ"
  default     = "t3.micro"

  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "t3 系インスタンスのみ使用可"
  }
}
# dev.tfvars
instance_type = "t3.micro"
replica_count = 1

# prod.tfvars
instance_type = "t3.large"
replica_count = 3
terraform apply -var-file="prod.tfvars"

5. secrets を tfvars に書かない

# ❌ tfvars にシークレットを書く(git に入る危険)
db_password = "mysecretpassword"

# ✅ AWS Secrets Manager or Parameter Store から取得
data "aws_ssm_parameter" "db_password" {
  name = "/prod/db/password"
}

resource "aws_db_instance" "main" {
  password = data.aws_ssm_parameter.db_password.value
}

6. Plan を必ずレビューする

# CI で plan 結果を PR に投稿
terraform plan -out=tfplan
terraform show -json tfplan | jq '.resource_changes[] | select(.change.actions != ["no-op"])'

破壊的変更(destroy/replace) を含む場合は追加承認を必須にする。

7. import で既存リソースを取り込む

# 既存の AWS リソースを state に取り込む
terraform import aws_s3_bucket.main my-existing-bucket

# Terraform 1.5+ からは import ブロックも使える
import {
  to = aws_s3_bucket.main
  id = "my-existing-bucket"
}

8. tflint・tfsec でセキュリティチェック

# tflint: Terraform の構文・ベストプラクティスチェック
tflint

# tfsec: セキュリティ脆弱性スキャン
tfsec .

# Checkov: コンプライアンスチェック
checkov -d .
# .github/workflows/terraform.yml
- name: tfsec scan
  uses: aquasecurity/tfsec-action@v1.0.0
  with:
    working_directory: terraform/

9. Drift 検出

コンソールで手動変更されたリソースを検出します。

# plan で差分を確認(差分があれば drift)
terraform plan -refresh-only

# 定期的に CI で実行してアラートを送る

10. 命名規則の統一

locals {
  prefix = "${var.project}-${var.environment}"
  common_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr

  tags = merge(local.common_tags, {
    Name = "${local.prefix}-vpc"
  })
}

まとめ

優先度プラクティス
必須Remote State + DynamoDB Lock
必須シークレットを tfvars に書かない
推奨モジュール化 + 環境ディレクトリ分離
推奨CI で plan → 手動承認 → apply
推奨tfsec / Checkov でセキュリティスキャン

参考: Terraform 公式ドキュメント / tfsec