Renovateは、Terraformのprovider・module・Terraform本体のバージョンを検知して更新PRを作り、.terraform.lock.hclのハッシュも自分で書き換えます。ただ、既定設定のままだと「制約の範囲内ならlockファイルだけが更新される」「required_version = ">= 1.9.0"には更新PRが出ない」など、設定ファイルの見た目からは予想しにくい動きをします。この記事では、Renovate 44.148.0(2026年10月8日公開)を手元のTerraformリポジトリに対して実行した結果をもとに、どの設定で何が変わるのかを整理します。
まとめ:RenovateでTerraformを更新するときの要点
- 対象:
**/*.tfと**/*.tofuを自動で走査し、required_providers・module・required_versionなどを依存として拾います。ディレクトリごとの登録は不要です。 - 既定の更新方法:
rangeStrategyの既定値autoは、Terraformではupdate-lockfileとして動きます。~> 3.5の範囲内の新版は.terraform.lock.hclだけを更新し、範囲外のメジャー版が出たときは~> 2.1を~> 3.0のように制約ごと書き換えます。 - Terraform本体:
>= 1.9.0のような下限だけの制約は既定ではPRが出ません。追いかけるならrequired_versionだけrangeStrategy: "bump"にします。 - lockファイル:Renovateはハッシュを自分で計算・取得して書き込むので、Terraform CLIは要りません。定期的に全providerを更新する
lockFileMaintenanceは既定で無効です。 - 落とし穴:
minimumReleaseAgeを設定すると、公開日時を返さないgit-tagsで取得するmodule(GitLabなどのgit::ソース)は、更新PRが保留されたまま解けません。 - Dependabotとの差:Dependabotは
required_versionを依存として扱わず、Terraformのセキュリティアップデートにも対応していません。Terraformを中心に据えるならRenovateが有利です。
以下、検知範囲→既定設定での実測→設定の変え方→運用上の落とし穴の順に説明します。
RenovateのTerraform対応範囲
terraform managerが拾う依存と取得元
Renovateのterraform managerは、.tfと.tofuの中身を解析し、依存の種類ごとにdepTypeを付けます。depTypeは、後述のpackageRulesで「Terraform本体だけ設定を変える」といった絞り込みに使う名前です。
| 記述 | depType | バージョンの取得元 |
|---|---|---|
| required_providersのprovider | required_provider |
Terraform Registry |
| providerブロックのversion | provider |
Terraform Registry |
| moduleのsourceとversion | module |
Registry/GitHubタグ/Gitタグ |
| terraformブロックのrequired_version | required_version |
GitHub Releases(hashicorp/terraform) |
| tfe_workspaceのterraform_version | tfe_workspace |
HCP Terraform向け |
| helm_releaseのchart | helm_release |
Helmリポジトリ・OCI |
| docker_*・kubernetes_*のimage | docker_imageなど |
コンテナレジストリ |
required_versionの新版はGitHubのhashicorp/terraformリポジトリから引くため、セルフホストで動かす場合はGitHubのトークン(RENOVATE_GITHUB_COM_TOKEN)が必要です。トークンなしで実行すると「GitHub token is required for some dependencies」という警告が出て、Terraform本体の更新だけが止まりました。
providerとmoduleは、レジストリの指定がなければregistry.terraform.ioから取得します。OpenTofuのレジストリを使う場合は、packageRulesでmatchDatasources: ["terraform-provider", "terraform-module"]にregistryUrls: ["https://registry.opentofu.org"]を指定します。
terragrunt・.terraform-version・TFLintプラグインは別manager
Terraformの周辺ファイルは、それぞれ別のmanagerが担当します。どれも既定で有効なので、ファイルがあれば自動的に対象になります。
terragrunt:terragrunt.hclのterraform { source = ... }を更新します(Terragruntの基本で扱う構成)。terraform-version:tfenvなどが読む.terraform-versionを、hashicorp/terraformのGitHub Releasesから更新します。terragrunt-version:.terragrunt-versionを更新します。tflint-plugin:.tflint.hclのpluginブロックの版を更新します(TFLintの導入と設定で使うAWSルールセットなど)。
既定設定で検出される更新候補の実測
Renovate 44.148.0をnpmで入れ、config:recommendedだけを書いたrenovate.jsonを置いたリポジトリに対してrenovate --platform=local(更新候補の検出までを行い、PRは作らないモード)を実行しました。対象のmain.tfは次のとおりで、.terraform.lock.hclはrandom 3.5.1とnull 2.1.2を記録した状態です。
terraform {
required_version = ">= 1.9.0"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.5"
}
null = {
source = "hashicorp/null"
version = "~> 2.1"
}
}
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
}
2026年10月9日時点で検出された更新は次の4ブランチでした。
| 依存 | 現在の値 | 更新後 | ブランチ名 |
|---|---|---|---|
| random | ~> 3.5(lock 3.5.1) | lockのみ3.9.1 | renovate/random-3.x-lockfile |
| null | ~> 2.1(lock 2.1.2) | ~> 3.0(3.3.2) | renovate/null-3.x |
| vpcモジュール | 5.0.0 | 5.21.0 | renovate/terraform-aws-modules-vpc-aws-5.x |
| vpcモジュール | 5.0.0 | 6.7.3 | renovate/terraform-aws-modules-vpc-aws-6.x |
| required_version | >= 1.9.0 | PRなし | なし |
読み取れることは3つあります。1つ目は、randomのように制約の範囲内に新版があると、main.tfは変えずにlockファイルだけを3.9.1へ上げることです。制約の文字列を見てPRを待っていると「更新が来ない」と誤解しやすいところです。2つ目は、nullのように範囲外のメジャー版が出ると制約を~> 3.0へ置き換えることです。3つ目は、moduleのマイナー版とメジャー版が別のPRに分かれることで、これはseparateMajorMinorの既定値がtrueだからです。
この挙動の理由はRenovate 44.148.0のgetRangeStrategy実装にあります。rangeStrategyがautoのとき、managerがlockファイルの部分更新(updateLockedDependency)を実装していればupdate-lockfile、そうでなければreplaceに解決されます。terraform managerはこれを実装しているためupdate-lockfileになります。公式ドキュメントもupdate-lockfileの対応managerにterraformを挙げています。
rangeStrategyの選び方
同じリポジトリで"rangeStrategy": "bump"にすると、結果は次のように変わりました。
| 依存 | auto(既定) | bump |
|---|---|---|
| random(範囲内) | lockのみ3.9.1 | ~> 3.9 に書き換え |
| null(範囲外) | ~> 3.0 | ~> 3.3 |
| required_version | PRなし | >= 1.16.5 |
providerは既定のautoのままにするのが無難です。~> 3.5で互換範囲を宣言し、実際に使う版はlockファイルで固定するというTerraform本来の分担と一致するからです。bumpにすると下限が毎回上がるため、同じproviderを使う共有モジュールの利用側と制約が衝突しやすくなります(制約の書き方の判断はproviderのバージョン制約とlockファイルの運用判断を参照)。
Terraform本体だけは事情が違います。required_versionはlockファイルに記録されないので、autoでは範囲内の新版を反映する先がなく、PRが出ません。実行を許可するTerraformの下限を新しい版に合わせて引き上げたいなら、required_versionに限ってbumpを指定します。ただしrequired_versionは制約にすぎず、CIにインストールされるTerraformは変わりません。CIの実行版はtfenvなどが読む.terraform-version(terraform-version manager)で追いかけ、両方を同じPRで上げるようにします。実測ではhashicorp/terraformがconfig:recommendedに含まれるgroup:monoreposに該当し、ブランチ名はrenovate/terraform-monorepoになりました。最新版の確認方法はTerraformの最新バージョンとアップグレード手順にまとめています。
moduleをversion = "5.0.0"のように完全一致で書いている場合は、autoでもbumpでも新しい版番号へ置き換わります。例外はin-range-onlyで、設定ファイルを書き換えないためmoduleの版も更新されません。
.terraform.lock.hclの更新とlockFileMaintenance
Renovateがハッシュを計算する仕組み
Renovateはterraform initやterraform providers lockを呼びません。Terraform Registryのproviderを更新するときは、対象版の全プラットフォームのzipをダウンロードしてh1:ハッシュを計算し、SHA256SUMSからzh:ハッシュを取り、重複を除いて並べ替えて書き込みます(lockfile/hash.ts)。OpenTofu Registryではpackages APIが返すハッシュを優先し、取れない場合だけzipから計算します。hashicorp/random 3.9.1で同じ処理を呼び出すと、h1が12件、zhが13件返りました。
このハッシュで作ったlockファイルに対してTerraform 1.16.5(darwin_amd64)でterraform initを実行しても、lockファイルの内容は変わりませんでした。terraform providers lock -platform=linux_arm64も「found no need for changes」で終わり、ハッシュは追加されていません(検証用に手で組んだファイルには先頭の自動管理コメントが無かったため、その2行だけが付け足されました)。全プラットフォーム分のh1がそろっているので、macOSで作業する開発者とLinuxのCIが混在していても、ハッシュの追記による差分は起きません。
注意点は2つです。lockファイルが無いディレクトリでは何も更新しないので、.terraform.lock.hclはコミットしておきます。また、lockファイルを書き換える対象はrequired_providerとproviderだけで、moduleはlockファイルに関係しません。
lockFileMaintenanceによるproviderの定期一括更新
通常の更新PRは、制約の範囲内で新版が出たproviderを1つずつ扱います。lockFileMaintenanceを有効にすると、lockファイル内の全providerを制約の範囲内の最新版へまとめて上げるPRが別に作られます。既定値は無効で、有効にした場合のスケジュールはbefore 4am on monday、ブランチ名はrenovate/lock-file-maintenanceです。
{
"lockFileMaintenance": { "enabled": true }
}
複数providerの更新が1つのPRに混ざるので、terraform planで差分が出たときの原因の切り分けには手間がかかります。
PRの数と実行タイミングの制御
導入直後は、溜まっていた更新がまとめて検出されます。Renovateの既定値はprHourlyLimitが1時間あたり2件、prConcurrentLimitが同時に開いておけるPR 10件で、branchConcurrentLimitは未指定ならprConcurrentLimitと同じ値を引き継ぎます。上限を超えた分は、config:recommendedで有効になるDependency Dashboard(Issue)に一覧されます。
PRの数を減らす手段は、件数の上限よりgroupNameでまとめる方が効きます。次の設定は、providerのマイナー・パッチ更新を1本にまとめ、メジャー版は個別のPRに残す例です。schedule:earlyMondaysは新しいブランチを作る時間帯を月曜0時〜3時台に限るプリセットで、timezoneを指定しないとUTC基準になります。Renovate自体の起動間隔はこれとは別に決め、既存ブランチのリベースなどは既定(updateNotScheduled: true)では時間外にも行われます。
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", "schedule:earlyMondays"],
"timezone": "Asia/Tokyo",
"prHourlyLimit": 4,
"prConcurrentLimit": 8,
"lockFileMaintenance": { "enabled": true },
"packageRules": [
{
"matchManagers": ["terraform"],
"matchDepTypes": ["required_version"],
"rangeStrategy": "bump"
},
{
"matchManagers": ["terraform"],
"matchDepTypes": ["required_provider"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "terraform providers (non-major)"
},
{
"matchFileNames": ["envs/dev/**"],
"additionalBranchPrefix": "dev-"
},
{
"matchFileNames": ["envs/prod/**"],
"additionalBranchPrefix": "prod-"
},
{
"matchFileNames": ["envs/dev/**"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
]
}
範囲内のlockファイル更新もmatchUpdateTypes上はminorやpatchとして扱われるので、このグループに入ります(設定はrenovate-config-validatorで検証済み)。
環境別ディレクトリを分けて扱うmatchFileNames
envs/devとenvs/prodのように環境ごとにディレクトリを分けている場合、Renovateは両方を自動で検知します。検知のためにmatchFileNamesを書く必要はありません。
問題になるのは、同じproviderの同じ版の更新が1つのブランチにまとまることです。上の例からadditionalBranchPrefixのルールを外して実行すると、devとprodのrandom更新はどちらもrenovate/terraform-providers-(non-major)という同じブランチに入りました。この状態でdevだけ自動マージにしようとしても、PRは1本しかないため、devとprodを別々に適用できません。
matchFileNamesで環境のディレクトリを指定し、additionalBranchPrefixにdev-とprod-を付けると、ブランチがrenovate/dev-terraform-providers-(non-major)とrenovate/prod-terraform-providers-(non-major)に分かれました。上の設定そのもので実行した結果は、null・providerグループ・Terraform本体・lockFileMaintenanceが環境ごとに分かれた8ブランチです。接頭辞を{{parentDir}}-で自動にする書き方もありますが、envs/dev/app/main.tfのように階層が深いとdevもprodもappになって分離できないため、環境名を固定で書く方が確実です。devのPRだけ自動マージし、prodは人がplanを確認してから適用する、という運用はここまで分けて初めて成り立ちます。PR上でplanを確認する仕組みには、AtlantisやTerraformのCIパイプラインを組み合わせます。
自動マージは、terraform planをCIの必須チェックにしていないリポジトリでは使わない方が安全です。Renovateが見るのはCIの成否だけなので、planが走らなければ、providerのメジャー版で属性が変わるような破壊的変更もそのまま取り込まれます。planは削除や再作成を含む計画でも成功するため、自動マージを許すなら、terraform show -jsonで出したplanのresource_changesにdeleteが含まれたらCIを失敗させる条件まで組み込みます。
minimumReleaseAgeとinternalChecksFilterの落とし穴
minimumReleaseAgeは、公開から一定期間が過ぎた版だけを更新対象にする設定です。既定は未設定(待機なし)で、"3 days"のように指定します。Renovate 42からは、公開日時を取得できない版を「まだ期間を満たしていない」とみなす挙動(minimumReleaseAgeBehaviourの既定値timestamp-required)に変わりました。
Terraformでは、この公開日時を返す取得元と返さない取得元があります。
| 取得元 | 公開日時 |
|---|---|
| registry.terraform.io(provider・module) | 全版あり |
| registry.opentofu.org | 全版あり |
| app.terraform.io(HCP Terraformのmodule) | 最新版のみ |
| その他のprivate providerレジストリ | なし |
| git::https://github.com/…(github-tags) | あり |
| git-tagsで取得するgit::ソース(GitLabなど) | なし |
GitLab上のリポジトリをgit::https://gitlab.com/...?ref=v17.0.0で参照するmoduleを置き、minimumReleaseAge: "3 days"で実行したところ、ログに「Marking 45 release(s) as pending, as they do not have a releaseTimestamp」と出て、更新は保留扱いになりました。git-tagsでは公開日時が取得できないので、この設定のままでは何日待っても保留は解けません。なお、Bitbucketのgit::ソースはbitbucket-tagsとして扱われ、こちらは公開日時を取得できます。
保留中の版をどう扱うかを決めるのがinternalChecksFilterです。既定値のstrictでは保留中の版のPRを作らず、Dependency Dashboardにだけ表示します。flexibleは全版が保留中のときに最新の保留版でPRを作りますが、期間を満たした古い版へPRが差し戻される「ばたつき」が起きると公式ドキュメントが注意しています。社内Gitのmoduleを使うなら、そのmoduleだけminimumReleaseAgeを外すのが確実です。
{
"minimumReleaseAge": "3 days",
"packageRules": [
{
"matchDatasources": ["git-tags"],
"minimumReleaseAge": null
}
]
}
プライベートレジストリの認証と実行環境
実行方法は、GitHubにMend Renovate App(GitHub App)を入れる方法と、自分で動かすセルフホスト(GitHub Action・Docker・npmのCLI)に分かれます。HCP Terraformのprivate registryや社内Gitのmoduleを参照するなら、トークンを自社のSecretsで管理できるセルフホストが扱いやすくなります。
認証はhostRulesで、matchHostに対象ホスト、tokenに認証情報を指定します。トークンをrenovate.jsonに直接書くとリポジトリに残るので、セルフホストのグローバル設定(config.js)で環境変数から読み込みます。
// renovate-config.js(セルフホストのグローバル設定)
module.exports = {
platform: 'github',
repositories: ['example-org/infra'],
hostRules: [
{ matchHost: 'app.terraform.io', token: process.env.TFE_TOKEN },
],
};
GitHub Actionのrenovatebot/github-action(v46.3.7、既定のRenovateは44系)は、Renovateをコンテナで起動し、RENOVATE_で始まる環境変数など決まったものだけをコンテナへ渡します。TFE_TOKENのような独自名の変数はadditional-env-listで明示しないと届かず、認証エラーになります。
- uses: renovatebot/[email protected]
with:
configurationFile: renovate-config.js
token: ${{ secrets.RENOVATE_TOKEN }}
additional-env-list: |
TFE_TOKEN
env:
TFE_TOKEN: ${{ secrets.TFE_TOKEN }}
RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Actionのバージョンをタグで書くかコミットSHAで固定するかは、SHA Pinningの考え方に従って決めます。
DependabotとのTerraform対応の違い
GitHub Docs(2026年10月時点)とDependabotのソース(dependabot-coreのTerraformパーサー)で確認した、Terraformに関する範囲の違いです。両ツールの一般的な比較やdependabot.ymlの書き方はDependabotの設定とRenovateとの違いで扱っています。
| 項目 | Renovate | Dependabot |
|---|---|---|
| provider・module | 対応 | 対応 |
| .terraform.lock.hcl | 対応 | 対応 |
| required_version | 対応 | 抽出しない |
| .terraform-version・TFLintプラグイン | 対応 | 非対応 |
| 対応Terraformの版(公式表記) | 記載なし | 0.13〜1.15.x |
| GitHub以外のプラットフォーム | GitLab・Bitbucketなど | GitHub |
Dependabotのパーサーはmoduleとrequired_providersだけを依存として取り出し、required_versionを読む処理を持っていません。GitHub Docsの対応表でも、Terraformは「Version updates」のみ対応で「Security updates」は非対応です。
判断の目安として、provider数本とRegistryのmoduleだけを使う小さなGitHubリポジトリなら、設定が少なく済むDependabotで足ります。Terraform本体の版を揃えたい、環境ごとに自動マージの扱いを変えたい、GitLabでも同じ運用をしたい、のどれかに当てはまるならRenovateを選びます。両方を同じリポジトリで動かすと同じ更新のPRが二重に開くので、どちらか一方に寄せてください。
よくある質問
Renovateのrangestrategyをbumpにするとproviderはどう変わりますか?
範囲内の新版でも制約の下限を書き換えます。~> 3.5で3.9.1が出ると、既定のautoはlockファイルだけを更新しますが、bumpは~> 3.9に変えます。providerはautoのまま、required_versionだけbumpにする使い分けが扱いやすい構成です。
prHourlyLimitの既定値はいくつですか?
1時間あたり2件です。同時に開いておけるPRの上限prConcurrentLimitの既定値は10件です。上限で止まった更新はDependency Dashboardに表示されます。
RenovateとDependabotの違いはTerraformでは何ですか?
大きな違いはrequired_versionの扱いです。RenovateはTerraform本体の版を更新できますが、Dependabotは依存として抽出しません。.terraform-versionやTFLintプラグインの更新もRenovateだけが対応しています。
Renovateが更新したlockファイルでterraform initすると差分が出ますか?
Renovate 44.148.0とTerraform 1.16.5の組み合わせでは、版・制約・ハッシュとも変わりませんでした。Renovateが全プラットフォーム分のh1ハッシュを書き込むためです。差分が出る場合は、lockファイルのprovider版とrequired_providersの制約が食い違っていないかを確認します。
internalChecksFilterとは何ですか?
minimumReleaseAgeで保留中になった版をPRにするかどうかを決める設定です。既定値のstrictは保留中の版のPRを作らず、noneは保留を無視して最新版でPRを作ります。