🚀 はじめに
この記事でわかること
- Terraformの設計の全体像(フォルダの切り方・モジュールの作り方・環境(dev/stg/prod)の分け方・ステート管理)
- なぜ設計が大事か、そしてよくあるつまずきポイントを先回りで回避する考え方
- すぐ試せる最小サンプルと、次に学ぶと良い道しるべ
✅ 概要解説
Terraformの“設計”とは何か
ざっくり言うと、レゴの設計図のことです。
どんな箱(フォルダ) に、どの部品(モジュール) を入れて、街(環境) ごとにどう並べ替えるか。
さらに、完成写真(状態=ステート) をどこに保管するかを決めるのが設計です。

- フォルダ構成… 作る物(ネットワーク・DBなど)と、使う場所(dev/stg/prod)をどう分けるか
- モジュール設計… レゴの「何度も使える下ごしらえ」パーツ化
- ステート管理… いまの完成形(状態)を安全に記録・共有する
- 運用フロー… 変更はどうレビューして、どう適用(apply)するか
ポイント:設計がきれいだと、「増築」「引っ越し」「片付け」がラクになります。
何のためにあるのか(設計が大事な理由)
- 事故を防ぐ(削除ミス・上書きミス・人によるバラつき)
- 再利用が効く(同じ作りを別環境で素早く展開)
- レビューしやすい(どこが変わるか差分が読みやすい)
- チームで回せる(誰が触っても同じルールで動く)
設計がないとどうなるの?
- ぐちゃぐちゃに成長して壊すのが怖い(どこに何があるか不明)
- 環境ごとの差が増え、“本番だけ違う”地雷が生まれる
- ステートが人のPCにだけある → 事故・紛失・衝突(同時更新)
- レビューが困難 → ミスが見抜けず「動いたらOK」文化に逆戻り
どんな場面で役立つ?
- 個人の学習〜小さなチーム:まずはフォルダとモジュールを整えるだけで運用が楽に
- 複数クラウド・複数環境:再利用とステートの分離で安全にスケール
- 長期運用:CI/CD、Lint、セキュリティチェックを足して “壊れにくい仕組み” に
🧱 設計の基本パターン(フォルダ構成)
A. “live + modules” 方式(いちばん読みやすい定番)
repo-root/
├─ modules/ # 再利用する部品置き場(レゴのパーツ箱)
│ ├─ storage-bucket/ # 例: S3バケットを作るモジュール
│ │ ├─ main.tf
│ │ ├─ variables.tf
│ │ └─ outputs.tf
│ └─ ... # 他にも vpc/, rds/, dns/ など増やす
└─ live/ # 実際に使う“現場”の設計図(街ごと)
├─ dev/
│ └─ app/ # サブシステム単位で分けてもOK
│ ├─ main.tf
│ └─ terraform.tfvars
├─ stg/
│ └─ app/
└─ prod/
└─ app/
- modules/:何度も使える部品(バケット、VPCなど)
- live/:実際の環境(dev / stg / prod)側の呼び出し
メリット:見通しが良く、レビューしやすい。環境を複製するのも簡単。
B. Workspace 方式(小規模〜単一チームで手早く)
- 同じフォルダで
dev / stg / prodをWorkspace名で切り替えるやり方 - スピード重視には向くけど、誤操作(workspace切替忘れ) が怖い
- チーム・規模が大きいなら A方式(live+modules)推奨
目安:まずは A方式。
Workspace は学習用や超小規模での運用に向きます。
🧩 モジュール設計(レゴの“良いパーツ”とは)
- 名前は“やること”が伝わるように(例:
storage-bucket、vpc-basic) - 入力(variables)は必要最小限。迷うフラグを山ほど作らない
- 出力(outputs)は次に使う人が助かる情報だけ
- バージョン固定(GitのタグやRegistryの
versionで) - READMEに使い方とサンプルを一緒に書く
コツ:「DRY(重複排除)」より “読みやすさ・驚きの少なさ” を優先。
なんでも詰め込み過ぎた“万能モジュール”はかえって難しくなります。
🧾 ステート(状態)管理:事故を防ぐ“保管庫”
ステート(terraform.tfstate)は、今の完成形(クラウドの実物)と設計図をつなぐ答え合わせ帳。
ローカル(自分のPC)に置かないのが鉄則です。
よく使うリモート保管先(バックエンド)
- AWS:
s3+ DynamoDBロック - Azure:
azurerm - GCP:
gcs
理由:みんなで共有できて衝突(同時編集)を防げる。バックアップや権限管理もやりやすい。
🧪 最小サンプル(AWSのS3バケットを“設計通り”に作る)
やること:
- modules に「バケット作る部品」を作る
- live/prod でそれを呼び出す
- ステートは S3(+DynamoDBロック)に置く
1) モジュール(modules/storage-bucket)
# modules/storage-bucket/variables.tf
variable "bucket_name" {
description = "作成するS3バケット名(世界で一意)"
type = string
}
variable "force_destroy" {
description = "中身があっても削除を許可するか(学習用はfalse推奨)"
type = bool
default = false
}
# modules/storage-bucket/main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
force_destroy = var.force_destroy
}
output "name" {
value = aws_s3_bucket.this.bucket
description = "作成されたS3バケット名"
}
2) 実環境(live/prod/app で呼び出し)
# live/prod/app/main.tf
terraform {
required_version = ">= 1.5.0"
backend "s3" {
bucket = "my-tfstate-prod" # 事前に用意
key = "prod/app/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "my-tfstate-lock" # 事前に用意(ロック用)
encrypt = true
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
module "bucket" {
source = "../../../modules/storage-bucket"
bucket_name = var.bucket_name
force_destroy = false
}
# live/prod/app/variables.tf
variable "aws_region" {
type = string
description = "AWSリージョン"
default = "ap-northeast-1"
}
variable "bucket_name" {
type = string
description = "S3バケット名(世界で一意)"
}
# live/prod/app/terraform.tfvars
bucket_name = "my-company-prod-app-bucket-12345"
使い方(例)
cd live/prod/app- 初回のみ
terraform init(バックエンド初期化)terraform plan(差分を確認)terraform apply(適用)
注意:S3バケット名は世界で一意。被っていたら別名にしましょう。
🔐 セキュリティと信頼性の“設計観点”
- 機密情報はコードに書かない
terraform.tfvarsに秘密を直書きしない- 代わりに環境変数(
TF_VAR_xxx)、Secrets Manager / Vaultを利用
- バージョン固定
- Terraform本体・プロバイダー・モジュールに上限と下限を付ける
- クリティカルな削除防止
lifecycle { prevent_destroy = true }の活用(DBなど)
- CI/CDでの2段階適用
- PRで plan(レビュー)→ mainマージで apply(承認・監査ログを残す)
- Lint / 静的解析
terraform fmt -check/terraform validate- tflint / tfsec / checkov で早期に匂いを検出
🧭 環境分けの設計(dev / stg / prod)
- ステートは環境ごとに完全分離(S3キーやGCSパスを分ける)
- 権限も分離(prodは厳しめのロール)
- 差分最小:devとprodで同じモジュールを使い、変えるのは入力だけ
- 命名規則をあらかじめ決める(例:
{org}-{env}-{system}-{component})
指針:「本番だけ特別」を作らない。
特別対応はモジュールの入力で吸収し、設計は同じに保つのが安全です。
🧱 依存関係と分割の考え方
- “大きくしすぎない”(VPC・DB・アプリなど論理単位で分割)
- 出力(outputs)で隣に渡す(例:VPCのIDをアプリ側に)
- 必要なら remote_state で参照(チーム間でも“読み取り専用”で使う)
やりすぎ注意:細切れにしすぎると適用順序や可視性がつらくなる。
まずはシステムごと(network / data / app など) の単位が扱いやすいです。
🛠️ 小さく始めて段階的に強くする運用フロー
- 最小構成を live + modules で作る
- リモートステートに移して共同管理
- PRで plan をレビュー(差分に慣れる)
- CI/CDで
fmt/validate/tflint/tfsecを自動化 - タグ付け・命名規則を整備(コスト可視化や検索が楽に)
- 監査ログ(誰がいつapplyしたか)を残す
ゴール:人の“うっかり”を仕組みで防ぐ。
設計は最初から完璧でなくてOK。段階的に磨いていきましょう。
💡 小話・豆知識・逸話
- 名前の由来:「Terraform」は “地形を作る/変える” の意味。
インフラ(クラウドの“地形”)をコードで形作る発想が名前に込められています。 - “動けばOK”の罠
はじめは動かすのが嬉しい。でも設計がゆるいと後が地獄。
設計=未来の自分へのやさしさだと思うと頑張れます。 - レゴ哲学
“誰が見てもどの箱に何が入っているか”がわかれば勝ち。
驚きが少ない=レビューが速い=事故が減るの三方良し。
📚 参考リンク
公式サイト・ドキュメント
- Terraform 公式(トップ)
https://www.terraform.io/ / https://developer.hashicorp.com/terraform - Modules(モジュールの基本)
https://developer.hashicorp.com/terraform/language/modules - Providers 要件(バージョン固定)
https://developer.hashicorp.com/terraform/language/providers/requirements - Workspaces
https://developer.hashicorp.com/terraform/language/state/workspaces - Terraform Registry(公開モジュール)
https://registry.terraform.io/
🛠️ 関連テーマ・次に理解すると良いこと
- Terraform CLIワークフロー(
init/plan/apply/destroyの意味と使い分け) - Import / Drift検知(既存リソースの取り込み、差分の扱い方)
- テストと品質(
terraform validate/tflint/tfsec/checkovの導入) - CI/CD設計(PRでplan、mainでapply、承認フロー・監査ログ)

【初心者向け】CI/CD(自動デプロイ)をやさしく解説|仕組み・メリット・イメージがつかめる入門ガイド
- 複数アカウント/プロジェクト運用(範囲ごとの分割と権限設計)
- Terragrunt(大規模での共通化・繰り返しパターンの整理)
🎯 まとめ
- 設計は“レゴの箱の並べ方”:
live + modulesがわかりやすい定番 - モジュールは薄く・分かりやすく:入力最小、出力は必要十分、READMEを添える
- ステートは必ずリモート:
s3/azurerm/gcsなどで共有とロックを実現 - 環境差分は入力で吸収:設計は同じ、変えるのは値だけ
- CI/CDとLintで“うっかり”防止:planレビュー → 承認 → apply
- 完璧を目指さず段階的に強化:まずは最小構成から始めよう
