02. 標準リポジトリ構成
Terraform プロジェクトのフォルダ構成は、後から環境を増やす時/モジュールを共有する時 に大きく効きます。最初に「公式が推奨している型」に合わせておきましょう。
なぜ構成にこだわるのか
Terraform は .tf ファイルが置かれた ディレクトリ単位 で 1 つの構成(= state)を作ります。dev と prod を同じディレクトリに置くと、片方を直したつもりで両方を壊します。
逆に最初から 環境ごとにディレクトリを分ける 設計にしておくと、dev で試して prod に持っていく流れが自然になります。
最小構成(学習・1 環境用)
my-infra/
├── .gitignore
├── README.md
├── terraform.tf # terraform { required_version, required_providers, backend }
├── providers.tf # provider "aws" { ... }
├── variables.tf # variable "..." { ... }
├── locals.tf # locals { ... }
├── main.tf # resource "..." { ... }
└── outputs.tf # output "..." { ... }
ファイル名は機能ではなく 役割 で分けるのが慣例です。Terraform は同じディレクトリの .tf をすべてマージして読むので、ファイルを分けても動作は同じ。読む人にやさしい分け方を選びます。
公式推奨ファイル名
terraform.tf / providers.tf / variables.tf / locals.tf / main.tf / outputs.tf / backend.tf。コードベースが大きくなったら network.tf / compute.tf 等に機能分割する。
標準構成(modules + envs)
my-infra/
├── .github/
│ ├── workflows/
│ │ ├── plan.yml # PR で plan を回す
│ │ └── apply.yml # main マージで apply
│ ├── CODEOWNERS # レビュー必須者
│ └── dependabot.yml # 依存更新(任意)
├── .gitignore
├── .pre-commit-config.yaml # commit 前に fmt/validate
├── README.md
│
├── modules/ # 再利用可能な部品
│ ├── network/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md # ← README があるものだけ「外部公開可」
│ ├── compute/
│ └── storage/
│
└── envs/ # 環境ごと(= state ごと)
├── dev/
│ ├── main.tf # module "network" { source = "../../modules/network" }
│ ├── terraform.tfvars # この環境固有の変数値
│ └── backend.tf # この環境用 state の保存先
├── stg/
└── prd/
envs ディレクトリのねらい
- 各環境が 別の state ファイル を持つ。dev で apply しても prd は触らない
- 環境固有の値(インスタンスサイズ、CIDR 等)は
terraform.tfvarsに切り出す - 各環境の
main.tfは modules/* を呼び出すだけ の薄いコードにする
環境分離に Workspace は使わない
公式が明記
Terraform の Workspace 機能は「同じ構成で複数 state を持つ」ためのものですが、本番/検証の分離には不適 と公式が明言しています。理由は「同じ backend に複数 state を相乗りさせるため、認証情報・権限の分離ができない」から。環境分離は必ずディレクトリ分割。
モジュール内の標準ファイル
各モジュールは「最低限これだけ」のセットが推奨です。
modules/network/
├── main.tf # resource ブロック群(このモジュールが作るもの)
├── variables.tf # 入力(呼び出し元が渡す値)
├── outputs.tf # 出力(呼び出し元に返す値)
└── README.md # 何のためのモジュールか・使い方
呼び出し側はこうなります:
module "network" {
source = "../../modules/network"
vpc_cidr = "10.10.0.0/16"
public_subnet_cidrs = ["10.10.1.0/24", "10.10.2.0/24"]
}
# モジュールの output は module.<name>.<output_name> で参照
resource "aws_instance" "web" {
subnet_id = module.network.public_subnet_ids[0]
# ...
}
よくあるアンチパターン
| やりがちなこと | 何が起きるか | 正しい型 |
|---|---|---|
| 1 つのディレクトリに dev/prd 両方を書く | dev の変更で prd を破壊 | envs/ で分割 |
| Workspace で dev/prd 分離 | state は分かれるが認証情報を分離できない | envs/ で分割(公式推奨) |
| tfstate を Git にコミット | 機密漏洩 + 競合事故 | S3+DynamoDB のリモート backend |
| tfvars に AWS アクセスキーを書く | Git 履歴に永久に残る | 環境変数 / Secrets Manager / OIDC |
| 1 モジュールで全 AWS 構成 | 変更のたびに全部 plan、衝突多発 | 機能ごとにモジュール分割 |