TerragruntはTerraformやOpenTofuの手前で動くオーケストレーションツールで、環境ごとに繰り返し書いていたbackend設定やprovider定義を1か所にまとめ、複数のディレクトリ(ユニット)を依存順に実行します。2026年3月30日にv1.0.0が出て、run-allの廃止や設定ファイル名の推奨変更など、0.x時代の解説記事がそのまま使えない変更が入りました。この記事では、Terragrunt v1.1.6とOpenTofu 1.13.1で検証用の構成(local backendとterraform_dataだけの模擬モジュール)を実行した結果と、v1.1.6の公式ドキュメントをもとに、最小構成から旧版からの移行までを整理します。
まとめ:Terragruntの役割とv1.x導入時の要点
- 役割は「設定の重複排除」と「複数ユニットの依存順実行」。リソースの作成や差分計算はOpenTofu/Terraform本体が行う
- 現行の安定版はv1.1.6(2026年9月21日)。v1.xの間はCLIのコマンド・フラグとHCL設定の後方互換が公式に保証されている
- 共通設定は
root.hclに置き、各ユニットはfind_in_parent_folders("root.hcl")で読み込む。ルートをterragrunt.hclにする構成は非推奨 - 一括実行は
terragrunt run --all -- plan。run-allはv1.1.6では「unknown command」で止まる --filterでディレクトリを除外するときは./prod/**のように配下まで書く。./prodだけでは何も除外されない- S3バケットなどのバックエンド資源は既定では作られない。
terragrunt backend bootstrapか--backend-bootstrapで明示する - 環境が1つでモジュールも数個なら、Terraform単体かworkspaceで足りる。Terragruntは環境×ユニットの組み合わせが増えてから入れる
Terragruntの役割とTerraform・OpenTofuとの分担
ラッパーとして担う範囲と既定の実行バイナリ
通常のインフラリソースの作成や差分計算はOpenTofu/Terraformが担いますが、Terragrunt自身もバックエンドの作成・管理などでクラウドAPIを呼びます。各ユニットのterragrunt.hclを読み、共通設定のマージ、backend.tfやprovider.tfの生成、モジュールのダウンロードを済ませてから、.terragrunt-cache配下の作業ディレクトリでtofuまたはterraformを起動します。v1.0.0からはterraformブロックにsourceが無いユニットでも必ず.terragrunt-cacheが作られるようになりました。
どちらのバイナリを使うかは--tf-path(環境変数TG_TF_PATH)で決まり、v1.1.6のヘルプでは既定値が「tofu (on PATH)」です。PATHにtofuが無くTerraformだけがある環境で実行すると、Terragruntはterraformを呼びました。両方を入れているマシンでTerraformを使いたい場合は、--tf-path terraformを明示しないとOpenTofuで実行されます。OpenTofuとTerraformの機能差はOpenTofuとTerraformのライセンス・機能差と移行判断で整理しています。
v1.0以降の互換性保証とMITライセンス
TerragruntはGruntworkがMITライセンスで公開しており、GitHubのスター数は2026年10月9日時点のGitHub APIで9,874です。Terraformは1.6以降BUSL 1.1へ移りましたが、Terragrunt本体のライセンスは変わっていません。v1.0.0ではtflintの同梱をやめました。tflintがBUSLへ移ったため、同梱版が古い版のまま固定されていたことが理由です。
実務上の意味が大きいのは、公式の「Terragrunt 1.0 Guarantees」です。v1.xの間はCLIのコマンドとフラグを削除・改名せず、v1.0で有効だったHCL設定も有効なままと明記されています。裏を返すと、0.x系ではマイナーリリースでも破壊的変更が入っていました。v1.0より前の手順書は、旧コマンドや旧フラグ、ルート設定の記述を確認し、該当箇所を更新します。移行箇所は後半の対応表にまとめました。
インストール手順と対応バージョンの確認
Homebrew・公式スクリプト・Chocolateyでの導入
# macOS / Linux(Homebrew)
brew install terragrunt
# 公式インストールスクリプト(中身を確認してから実行する場合)
curl -sSfL --proto '=https' --tlsv1.2 -o install.sh https://terragrunt.com/install
less install.sh
bash install.sh
# Windows(Chocolatey)
choco install terragrunt
# 動作確認
terragrunt --version
Homebrewのformulaは2026年10月9日時点で1.1.6です。バイナリを直接取る場合はGitHub releasesからterragrunt_darwin_amd64.tar.gzなどOSに合うアーカイブを取得し、SHA256SUMSで照合します。公式手順ではGPG署名(鍵指紋68C8 0F86 DF98 E710 C0F2 2E2E 5777 74AC A847 CC49)、Cosign、gh release verify-assetによる検証も案内されています。v1.2.0は2026年10月7日時点でrc2の段階なので、CIでは1.1系の版を固定します。Terraform本体の入れ方はTerraformの最新バージョンと確認・アップグレード手順を参照してください。
OpenTofu・Terraformとの互換表と公式テストの範囲
| 本体の版 | 公式テスト対象になったTerragrunt | 2026年10月時点の現行版 |
|---|---|---|
| OpenTofu 1.12.x | 1.0.5以降 | ― |
| OpenTofu 1.13.x | 1.2.0以降(rc段階) | 1.13.1 |
| Terraform 1.14.x | 0.94.0以降 | ― |
| Terraform 1.15・1.16 | 表に未掲載 | 1.16.5 |
この表はTerragruntのCIでテストしている組み合わせで、ドキュメントには「実際の互換性はこれより緩い」と注記があります。手元ではv1.1.6とOpenTofu 1.13.1、Terraform 1.16.5の両方でplanとapplyが通りました。ただし表に無い組み合わせを本番に入れるなら、Terragrunt側の版をテスト対象に含まれる版へ上げるか、ステージングで一巡させてからにします。互換データはhttps://docs.terragrunt.com/api/v1/compatibility/opentofuからJSONでも取得できます。
root.hclとterragrunt.hclで組む最小構成
ディレクトリ構成とroot.hclの共通設定
live/
├── root.hcl # 全ユニット共通(backend・provider)
├── dev/
│ ├── env.hcl # dev固有の値
│ ├── network/terragrunt.hcl
│ └── app/terragrunt.hcl
└── prod/
├── env.hcl
├── network/terragrunt.hcl
└── app/terragrunt.hcl
modules/
├── network/main.tf
└── app/main.tf
modules配下は、依存順を確かめるためにAWSを使わない模擬モジュールにしています。実際の構成では、VPCやEC2を作るモジュールに置き換えます。
# modules/network/main.tf
variable "cidr" { type = string }
resource "terraform_data" "vpc" { input = var.cidr }
output "vpc_id" { value = "vpc-${terraform_data.vpc.id}" }
# modules/app/main.tf
variable "vpc_id" { type = string }
variable "instance_type" { type = string }
resource "terraform_data" "app" { input = "${var.vpc_id}/${var.instance_type}" }
output "summary" { value = terraform_data.app.input }
Terragruntではモジュールを呼び出す単位を「ユニット」と呼び、1ユニット=1ディレクトリ=1つのstateになります。共通設定はlive/root.hclに一度だけ書きます。
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "example-tfstate-123456789012"
key = "${path_relative_to_include()}/tofu.tfstate"
region = "ap-northeast-1"
encrypt = true
use_lockfile = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOT
provider "aws" {
region = "ap-northeast-1"
}
EOT
}
path_relative_to_include()はroot.hclから見たユニットの相対パスを返すため、renderで展開するとdev/appユニットのkeyはdev/app/tofu.tfstateになりました。ユニットを増やしてもstateのキーを手で振る必要がありません。このS3版のroot.hclは、AWSに接続しないhcl validateとrenderで確認しました。依存順の実行は、backendをlocalに差し替えた同じ構成で検証しています。use_lockfile = trueはDynamoDBを使わないS3ネイティブのロックで、Terraform 1.10で追加(1.11でGA)、OpenTofuは1.10から使えます。stateの構造やロックの仕組みはTerraform stateとS3バックエンドの安全な扱い方で解説しています。
ユニット側のterragrunt.hclと設定の継承
include "root" {
path = find_in_parent_folders("root.hcl")
}
locals {
env = read_terragrunt_config(find_in_parent_folders("env.hcl")).locals
}
terraform {
source = "../../../modules//network"
}
inputs = {
cidr = local.env.cidr
}
includeでroot.hclを取り込み、read_terragrunt_configで環境ごとのenv.hclを読みます。dev/env.hclの中身は次の4行だけです。
locals {
cidr = "10.10.0.0/16"
instance_type = "t3.small"
}
prodはenv.hclのcidrとinstance_type(例:m7i.large)だけを変えれば済み、ユニット側のterragrunt.hclはdevとprodで同じ内容になります。sourceの//はモジュールの取得範囲とサブディレクトリの区切りで、modulesディレクトリ全体をキャッシュへコピーしたうえでnetworkを実行対象にします。モジュール側の分割粒度はTerraform moduleの分割粒度とoutput設計が参考になります。
dependencyとmock_outputsによるユニット間の値の受け渡し
appユニットはnetworkユニットが出力するVPC IDを使います。app/terragrunt.hclは、networkと同じincludeとlocalsを書き、terraformブロックのsourceだけを../../../modules//appに変えます。そのうえで次のdependencyとinputsを足します。
dependency "network" {
config_path = "../network"
mock_outputs = {
vpc_id = "vpc-mock"
}
mock_outputs_allowed_terraform_commands = ["validate", "plan"]
}
inputs = {
vpc_id = dependency.network.outputs.vpc_id
instance_type = local.env.instance_type
}
mock_outputsは、依存先をまだ一度もapplyしていない段階でplanを通すための仮の値です。mockを書かずに未適用のスタックでrun --all -- planを実行すると、networkのplanは成功し、appは「detected no outputs」のエラーで失敗しました。mock_outputs_allowed_terraform_commandsにapplyを含めてはいけません。含めると、依存先の出力がない場合に仮のIDがapplyへ渡ります。リソースやproviderによって作成エラーや誤った値の保存につながるため、applyは許可対象から外します。
run –allとfilterによる複数環境の一括実行
依存順に実行されるrun –allの挙動
cd live/dev
terragrunt run --all -- plan
# 実行結果の末尾(OpenTofu 1.13.1)
# ❯❯ Run Summary 2 units 971ms
# Succeeded 2
live/devで実行すると、Terragruntはdependencyブロックから依存グラフを作り、networkを先に、appを後に実行します。依存グラフはterragrunt dag graphでDOT形式で確認でき、この構成では"app" -> "network"と出力されました。新規環境でrun --all -- applyを流すと、networkのapply完了後にappが実際のVPC IDを受け取って作成されます。並列数は--parallelismで制限できます。
run --allでapplyすると、各ユニットへの-auto-approveが自動で付きます。実行前に「Are you sure you want to run ‘terragrunt apply’ in each unit of the run queue displayed above? (y/n)」という確認が一度だけ出ますが、--non-interactiveを付けたCIではそれも出ません。--no-auto-approveは自動承認フラグの追加を止めるだけです。一括実行では標準入力を共有するため、個別承認が必要ならユニット単位で実行するか、CI側に承認工程を設けます。
–filterのパス一致条件とprod配下の除外
環境を絞る指定は、v1.xでは--filterに集約されています(旧--queue-exclude-dirなども内部ではfilterへ変換されます)。注意点は、パス式がユニットのディレクトリそのものに一致する点です。
# 誤り:ユニットの親ディレクトリ名だけでは一致しない(4ユニットすべてが対象のまま)
terragrunt find --filter='!./prod'
# 正しい:配下のユニットまで含めて除外する(dev/app と dev/network だけが残る)
terragrunt find --filter='!./prod/**'
terragrunt run --all --filter='!./prod/**' -- plan
live直下で--filter='!./prod'を指定しても、prodはユニットではなく親ディレクトリなので一致せず、dev・prodの4ユニットすべてが残りました。同じ理由で、旧フラグの--queue-exclude-dir prodを付けたrun --all -- validateも4ユニット全部を実行しています。本番除外のつもりで書いた指定が空振りすると、そのままprodへapplyが流れます。本番を触る前にterragrunt findへ同じfilterを渡し、対象ユニットの一覧を確認する手順をCIに入れておくのが確実です。
–backend-bootstrapの既定値とバックエンド資源の作成条件
0.x時代の解説には「S3バケットが無ければTerragruntが自動で作る」と書かれたものが多くあります。v1.1.6のドキュメントでは--backend-bootstrapの既定値はfalseで、明示しない限りバケットもDynamoDBテーブルも作られません。
# 事前に一度だけ、S3バケットなどのバックエンド資源を作る
terragrunt backend bootstrap
# または実行時に作成を許可する
terragrunt run --all --backend-bootstrap -- plan
掲載設定でbootstrapすると、S3バケットにはバージョニングとサーバーサイド暗号化が設定されます。アクセスログは既定では無効で、有効化にはaccesslogging_bucket_nameの指定が必要です。dynamodb_tableを書いていればロック用テーブル(主キーLockID)も作られます。既存バケットの設定とremote_stateの記述が違うと、TerragruntはS3側を記述に合わせて更新します。手で管理しているバケットならdisable_bucket_update = trueで止めておきます。
0.x時代の設定から移行するときの対応表
| 0.x時代の書き方 | v1.xでの書き方 | 旧書式のまま実行した結果 |
|---|---|---|
terragrunt run-all plan |
terragrunt run --all -- plan |
unknown commandで停止 |
--terragrunt-non-interactive |
--non-interactive |
tofuへ渡りflag未定義エラー |
TERRAGRUNT_*環境変数 |
TG_* |
― |
ルートのterragrunt.hcl |
root.hcl |
非推奨の警告 |
find_in_parent_folders() |
find_in_parent_folders("root.hcl") |
root.hclを見つけられずエラー |
--queue-exclude-dir |
--filter='!./dir/**' |
filterの別名として動作 |
| 同梱のtflint | tflintを別途インストール | v1.0.0で同梱を廃止 |
一番多く引っかかるのはrun-allです。v1.1.6では「Terragrunt no longer forwards unknown commands by default」と表示されて止まります。--terragrunt-接頭辞の旧フラグはTerragruntが解釈せずOpenTofuへそのまま渡すため、エラー文が「flag provided but not defined: -terragrunt-non-interactive」とOpenTofu側の表現になり、原因に気づきにくくなります。ルートの設定ファイルは、名前をroot.hclへ変え、子のfind_in_parent_folders()に引数を足すまでが1セットです。tflintをhookで呼んでいる場合は、ランナーへのtflint導入も必要になります(TFLintの導入と設定)。
workspace・Atlantis・HCP Terraformとの使い分けと採用しない条件
| 方式 | 環境の分け方 | stateの分離 | 解決する問題 |
|---|---|---|---|
| Terraform単体 | ディレクトリをコピー | ディレクトリごと | ― |
| terraform workspace | 同一コード+workspace名 | 同一backend内で分離 | 同じ構成の複製 |
| Terragrunt | ディレクトリ+共通設定の継承 | keyの設定によりユニット別に分離 | 設定重複と実行順 |
| Atlantis | リポジトリ内のプロジェクト | 各プロジェクトの設定に従う | PR上でのplan・apply |
| HCP Terraform | workspace(SaaS) | SaaS側で管理 | 実行基盤・権限・履歴 |
AtlantisやHCP Terraformは「どこで誰がapplyするか」を扱う道具で、Terragruntは「何をどの順で実行するか」を扱います。役割が重ならないので併用できます。Atlantisの公式ドキュメントにも、カスタムworkflowからterragruntを呼ぶ設定例が載っています(AtlantisでTerraformをPR運用する構成と権限設計)。実際に比較対象になるのはworkspaceです。workspaceは全環境が同じコードを共有するので、prodだけモジュールの版を先に上げる、といった差分を持たせにくくなります。判断基準はterraform workspaceの使い方と環境分離の判断基準で比較しています。
次の条件に当てはまるなら、Terragruntは入れません。環境が1つで、モジュールが数個しかない場合。チームがTerraformのモジュール設計にまだ慣れていない場合。これらに当てはまるときは、Terragrunt固有のHCL関数、.terragrunt-cache、依存グラフといった仕組みを調べる手間の方が、重複を減らす効果より大きくなります。導入するのは、dev・stg・prodのように環境が増え、同じbackend設定を複数箇所で変更する作業が繰り返され、設定漏れの確認が負担になった段階が目安です。
運用とCIで詰まりやすい箇所
- キャッシュの肥大:ユニットごとに
.terragrunt-cacheへモジュールとproviderが展開されます。v1.1.0からはContent Addressable Store(CAS)が既定で有効になり、同じソースのダウンロードは重複排除されます(無効化は--no-cas)。OpenTofu 1.10以降ではproviderの共有キャッシュが既定で有効です。Terraformでは--provider-cache(TG_PROVIDER_CACHE)でTerragruntのProvider Cache Serverを有効にできます - シンボリックリンク経由のパス:macOSの
/tmp(実体は/private/tmp)の下で実行したところ、dag graphの依存先が実パスで表示されました。依存関係が一致せず、新規環境のrun --all -- applyでappがnetworkより先に走って失敗しました。/private/tmpで実行し直すと正しい順に完了しています。CIのワークスペースがリンク経由なら実パスで実行してください - CIの非対話実行:
--non-interactive(TG_NON_INTERACTIVE=true)を付けないと、run --allの確認プロンプトで止まります。plan結果をPRに残す設計はTerraformのCI導入ガイドと同じ考え方で組めます - 依存先に追加したoutput:
dependencyは依存先のstateからoutput -jsonで値を読みます。依存先に既存のoutputがある場合、既定のno_mergeでは追加したoutputをmockで補完せず、未適用の属性への参照がエラーになります。依存先をapplyしてstateのoutputを更新してください。適用前のplanでmockに補わせたい場合はmock_outputs_merge_strategy_with_state = "shallow"を指定します
# .terragrunt-cache をまとめて削除する
find . -type d -name ".terragrunt-cache" -prune -exec rm -rf {} +
ディスクを圧迫したときや、モジュールの差し替えが反映されないときに実行します。この記事のS3構成ではstateはキャッシュ外に保存されます。ただし、ローカルbackendのstateや保存済みplanがキャッシュ内にある場合は一緒に消えるため、削除前に保存場所を確認してください。
よくある質問
Terragruntとは何ですか?
Gruntworkが開発するオープンソース(MIT)のツールで、TerraformやOpenTofuのコードを複数環境・複数ディレクトリに展開するときの設定の重複と実行順を管理します。2026年10月時点の安定版はv1.1.6です。
TerraformとTerragruntの違いは何ですか?
Terraformはインフラの差分計算とAPI呼び出しを担う本体で、Terragruntはその実行を束ねるラッパーです。Terraform単体でも設定は再利用できますが、環境別のルートモジュールではbackendやproviderの設定が重複しがちです。Terragruntではroot.hclに一度書けば各ユニットが継承し、ユニット間の値の受け渡し(dependency)と依存順の一括実行も担います。
Terragruntはどうやってインストールしますか?
macOS・Linuxはbrew install terragrunt、Windowsはchoco install terragruntが手軽です。公式のインストールスクリプトやGitHub releasesのバイナリも使えます。導入後はterragrunt --versionで版を確認し、CIでは版を固定します。
root.hclとterragrunt.hclはどう使い分けますか?
terragrunt.hclは各ユニットのディレクトリに置く設定で、そのディレクトリが実行単位であることを示します。root.hclは全ユニットに共通する設定を置く親ファイルで、ユニット側からfind_in_parent_folders("root.hcl")で読み込みます。以前はルートにもterragrunt.hclを置くのが定番でしたが、ユニットと区別できずrun --allの対象判定が分かりにくくなるため、現在は非推奨です。
run-allが動かないときはどうすればよいですか?
v1.xではrun-allが廃止されており、terragrunt run --all -- planのようにrunコマンドへ--allを付け、OpenTofu/Terraformへ渡すコマンドを--の後に書きます。--terragrunt-で始まる旧フラグも接頭辞を外した名前に変わっているので、スクリプト全体を置き換えてください。