Bicepは、ARMテンプレート(JSON)を生成するための記述言語です。デプロイ時にBicep CLIが .bicep をARM JSONへ変換し、Azure Resource Managerが受け取るのはそのJSONです。両者は競合する選択肢ではなく、「書く形式」と「実行される形式」の関係にあります。
この記事では、2026年10月5日公開のBicep CLI 0.48.1で実際にビルドとdecompileを行い、どのBicepがどのJSONになるか、型チェックが止めるミスと素通りさせるミス、JSONのまま残すべき場面、既存テンプレートを移行するときに崩れる箇所を確認した結果をまとめます。BicepのインストールやデプロイのコマンドはAzure Bicepとは?インストールからbicep build・デプロイまでの実務手順で扱っています。
まとめ:ARMテンプレートとBicepの違いと使い分けの要点
- BicepはARM JSONへ変換される記述言語。Learnは「JSONテンプレートの機能を失わない透過的な抽象化」と説明し、ARMテンプレートで有効なリソース型・APIバージョン・プロパティはBicepでもそのまま使えます。
- 新規に書くならBicep。ARMテンプレートの概要ページ自体が、IaCを検討するならBicepを見るよう勧めています。
- 33行のBicepは89行のJSONになりました(0.48.1で実測)。
dependsOnやreference()はBicep側で書かなくても自動で出力されます。 - 型チェックで止まるのは型の不一致・必須プロパティ欠落・未定義の名前・範囲制約の違反。ストレージアカウントで試した範囲では、未知のプロパティは警告止まりでビルドが成功し、SKU名の誤りは指摘されませんでした。
- 「Deploy to Azure」ボタン、Azure Policyの
deployIfNotExists、マネージドアプリケーションのmainTemplate.jsonはJSONが必要です。Bicepで書いてbicep buildで出力します。 - 既存のJSONは捨てなくてよく、Bicepから
moduleとしてそのまま呼べます。移行するときは decompile の出力を下書き扱いにし、Microsoftの5フェーズ(変換・移行・リファクタリング・テスト・デプロイ)で進めます。
ARMテンプレートとBicepの関係:記述形式と生成JSON
Microsoft LearnのBicep概要は、Bicepを「Resource Manager JSONテンプレートに対する透過的な抽象化で、JSONテンプレートの機能を失わない」と位置づけ、デプロイ時にBicep CLIがBicepファイルをJSONテンプレートへ変換すると説明しています。リソースプロバイダーが新しいリソース型やAPIバージョンを出せば、ツールの更新を待たずにBicepから使えるとも明記されています。
つまり、デプロイの仕組み・状態の持ち方・使えるリソースは同じで、違うのは人が書くファイルの形式です。比較すると次のようになります。
| 観点 | ARMテンプレート(JSON) | Bicep |
|---|---|---|
| ファイル | .json | .bicep(パラメーターは .bicepparam) |
| Azureが受け取る形式 | JSONそのもの | 変換後のJSON |
| 状態ファイル | なし(Azure側が保持) | なし(同左) |
| 値の参照 | [parameters(‘x’)] などの式 | 名前で直接参照 |
| 依存関係 | dependsOn と reference() の暗黙依存 | シンボル参照から自動(dependsOnも可) |
| 部品化 | リンク・入れ子テンプレート | module(JSONも呼べる) |
| 共有 | テンプレートスペック | レジストリ+テンプレートスペック |
| 宣言の配置 | セクションごとに固定 | ファイル内のどこでも可 |
サイズの上限もJSONの側で決まります。ARMテンプレートは展開後で4MB・リソース定義1件あたり1MB、パラメーター256・リソース800(copyの数を含む)・出力64が上限です。Bicepで短く書けても、ループを展開したJSONがこの上限に当たれば失敗します。
Azure以外も含めてIaCを統一したい場合はBicepの守備範囲外です。その比較はTerraformでAzureを構築する手順|azurerm 5系の認証とbackend設計とPulumiとは?読み方・対応言語・Terraformとの違い・料金を実例で解説を参照してください。
Bicepの構文とARM JSONの対応表
Bicepの各構文が、ビルド後にどのJSONになるかを0.48.1の出力から拾いました。decompileはこの対応を逆にたどります。
| Bicep | ARM JSON(ビルド後) |
|---|---|
| param env string = ‘dev’ | parameters.env(type・defaultValue) |
| @allowed / @description | allowedValues / metadata.description |
| var x = … | variables.x |
| ‘st${env}’ | [format(‘st{0}’, parameters(‘env’))] |
| a == b ? x : y | [if(equals(a, b), x, y)] |
| [for c in list: {…}] | copy(name・count)と copyIndex() |
| resource … = if (cond) | condition |
| parent: sa | 名前の連結+dependsOn |
| sa.properties.x | reference(resourceId(…), ‘API版’).x |
| module m ‘./x.bicep’ | Microsoft.Resources/deployments(入れ子) |
| type(ユーザー定義型) | definitions(languageVersion 2.0) |
bicep buildが出力するARM JSONの中身(0.48.1で実測)
33行のBicepが89行のJSONになる変換例
ストレージアカウント、Blobサービス、コンテナー2つ(ループ)、本番だけの削除ロック(条件付き)、出力1つを持つBicepを用意しました。
@description('環境名')
@allowed(['dev', 'prod'])
param env string = 'dev'
param location string = resourceGroup().location
var storageName = 'st${env}${uniqueString(resourceGroup().id)}'
var containers = ['logs', 'images']
resource sa 'Microsoft.Storage/storageAccounts@2024-01-01' = {
name: storageName
location: location
sku: { name: env == 'prod' ? 'Standard_ZRS' : 'Standard_LRS' }
kind: 'StorageV2'
properties: { minimumTlsVersion: 'TLS1_2' }
}
resource blob 'Microsoft.Storage/storageAccounts/blobServices@2024-01-01' = {
parent: sa
name: 'default'
}
resource ctr 'Microsoft.Storage/storageAccounts/blobServices/containers@2024-01-01' = [for c in containers: {
parent: blob
name: c
}]
resource lock 'Microsoft.Authorization/locks@2020-05-01' = if (env == 'prod') {
name: 'no-delete'
scope: sa
properties: { level: 'CanNotDelete' }
}
output blobEndpoint string = sa.properties.primaryEndpoints.blob
bicep build main.bicep の出力は89行(最終行を含めて数えた値)で、元の約2.7倍でした。コンテナーのループと出力の部分を抜き出すと次のとおりです。
{
"copy": {
"name": "ctr",
"count": "[length(variables('containers'))]"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers",
"apiVersion": "2024-01-01",
"name": "[format('{0}/{1}/{2}', variables('storageName'), 'default', variables('containers')[copyIndex()])]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('storageName'), 'default')]"
]
}
...
"outputs": {
"blobEndpoint": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2024-01-01').primaryEndpoints.blob]"
}
}
ARM JSONでも reference() や list*() にリソース名を渡せば暗黙の依存になりますが、Learnのとおり子リソースと親の間には自動で付かず、リソースIDを渡した場合も暗黙の依存は作られません。JSONで手書きする場合、子リソースの名前を '親/default/子' の形で連結し、dependsOn に親の resourceId() を並べる作業が要ります。Bicepでは parent を1行書けば両方が出力され、削除ロックのように scope で参照しただけのリソースにも dependsOn が付きます。依存の書き漏れによる「親がまだ無い」エラーは、Bicepで書く限り起きにくくなります。
languageVersion 2.0へ切り替わる条件
上の出力には languageVersion が無く、resources は配列でした。同じストレージアカウントに type envName = 'dev' | 'prod' というユーザー定義型を1行足すと、出力は "languageVersion": "2.0" になり、resources はシンボル名("sa")をキーにしたオブジェクトへ変わりました。
Learnのテンプレート構文リファレンスによれば、ユーザー定義型・ユーザー定義関数・コンパイル時インポート(import)・実験的機能のいずれかを使うと2.0で出力されます。2.0では入れ子テンプレートの expressionEvaluationOptions の既定が inner になり、outer は使えません。生成JSONを他のツールで読み込んでいる場合、型を1つ足しただけで resources の形が変わる点に注意してください。
metadata._generatorに記録される生成元情報
Bicepから生成したJSONには metadata._generator が入り、name が bicep、version が 0.48.1.52935、内容から計算した templateHash が記録されます。この情報が残っていれば、手元のJSONがBicep由来かを判断する手掛かりになります(削除・編集はできるので確実な証明ではありません)。Bicep由来のJSONをdecompileすると「元の.bicepがあるならそちらを使うように」という警告が出ます。生成JSONは成果物であり、編集の起点にはしません。
Bicepの型チェックが止めるミスと止めないミス
Bicepの利点として「ビルド時に型チェックされる」とよく書かれますが、0.48.1で試すと止まるミスと止まらないミスがはっきり分かれました。
| 書いたミス | 結果 | ビルド |
|---|---|---|
| int のパラメーターに ‘two’ | Error BCP033 | 失敗(終了コード1) |
| resource に name が無い | Error BCP035 | 失敗 |
| 存在しないシンボル名を参照 | Error BCP057 | 失敗 |
| プロパティ名の綴り誤り | Warning BCP037 | 成功(JSONにそのまま出力) |
| SKU名 Standard_LRSX | 指摘なし | 成功 |
| kind に StorageV3 | 指摘なし | 成功 |
綴り誤りの例では、supportsHttpsTrafficOnly を supportsHttpsTrafic と書くと、BCP037が正しい候補を列挙するものの、終了コードは0で、誤ったプロパティ名がJSONへそのまま出力されました。LearnのBCP037の説明で重大度は「Warning / Error」とされ、0.48.1ではリソースのプロパティに対しては警告でした。linterのルールではないため、bicepconfig.json の analyzers.core.rules に書いても警告のままでした。
一方、linterのルールは重大度を error に上げられます。use-resource-symbol-reference を error にすると bicep build も bicep lint も終了コード1で止まりました(bicep lint は警告だけなら0を返します)。
なお、Bicep側で判定できる範囲制約は止まります。@minValue(3) を付けたintパラメーターの既定値を1にすると、BCP328で終了コード1になりました。ここから言えるのは、CIでビルドが通ってもデプロイの成功は保証されないということです。今回指摘されなかったSKU名や kind の値は、デプロイ時にAzure側で検証されます。ビルドの警告を出力に残して確認する運用に加え、本番前にはwhat-if(Bicepの実務手順の記事で扱っています)か検証環境へのデプロイで値を確かめてください。
ARMテンプレート(JSON)を残すべき場面
新規はBicepで書くとしても、JSONが必要な場面は残ります。いずれもBicepで書いて bicep build でJSONを出力すれば対応できます。
- 「Deploy to Azure」ボタン:GitHubやストレージ上のリモートARM JSONをデプロイする仕組みで、Learnはリモートの Bicep ファイルには対応しないと明記しています。
- Azure Policyの deployIfNotExists:ポリシー定義の
deploymentプロパティに、Deployments APIへ渡す形のテンプレートを丸ごと埋め込みます。入れ子テンプレートは使えますが、リンクされたテンプレートは使えません。 - マネージドアプリケーション:定義のパッケージには
mainTemplate.jsonが必須です。Learnの手順もBicepで書いてbuildでJSONにする流れです。 - Bicepを使わない利用者への配布:Bicepのモジュールレジストリに置いた部品は、別のBicepファイルからしかデプロイできません。テンプレートスペックならAPI・Azure PowerShell・Azure CLI・ポータルから直接デプロイでき、ポータルの入力フォームも定義できます。
既存のJSONテンプレートを当面そのまま使い続けたい場合は、Bicepの module から直接参照できます。次の例では、前章で生成したJSONを legacy.json という名前で同じフォルダーに保存しています。
module legacy './legacy.json' = {
name: 'legacy-storage'
params: { env: 'prod' }
}
output ep string = legacy.outputs.blobEndpoint
0.48.1でビルドすると、このmoduleは Microsoft.Resources/deployments の入れ子デプロイ(expressionEvaluationOptions 付き・mode は Incremental)として出力され、JSON側の出力値もBicepから型付きで参照できました。全面移行を待たずに、新しいリソースだけBicepで書き始められます。
既存のARMテンプレートをBicepへ移行する手順
Microsoftが推奨する5フェーズの進め方
Learnの移行ガイドは、次の5フェーズで進めることを推奨しています。
- 変換:既存のJSON(無ければ
az group export等でリソースを書き出したもの)をbicep decompileでBicepにします。この時点の出力は「完成品でもデプロイ可能でもない」とされています。 - 移行:新しい空のBicepファイルを作り、変換結果からリソースを1つずつ写します。ポータルやCLIのエクスポートで出てこないリソース(DependencyAgentWindows などのVM拡張)はここで書き足します。
- リファクタリング:APIバージョンの見直し、linterの指摘対応、名前の付け直し、
concat()やformat()の文字列補間への置き換え、子リソースの書き方の統一、モジュール分割、コメントと@description()の追加を行います。 - テスト:what-ifで差分を確認し、本番以外の環境へテストデプロイします。
- デプロイ:ロールバック計画を用意し、本番のパラメーターでwhat-ifを実行し、手動で一度デプロイしてからパイプラインへ組み込み、スモークテストで確認します。
パラメーターファイルは bicep decompile-params で .bicepparam へ変換できます。ARM JSONのパラメーター名にはピリオドを使えますが、Bicepの識別子は英数字とアンダースコアだけなので、decompileは Security.Authentication.AAD.Tenant を Security_Authentication_AAD_Tenant に置き換えます。パラメーターファイルやパイプラインから名前で値を渡している箇所は、合わせて直す必要があります。
手書きのJSONをdecompileした結果
手書き54行のJSON(concat() で組んだ名前、サブネットのプロパティcopyループ、内側スコープの入れ子テンプレート、copy付きの出力)をdecompileすると、終了コード0で34行のBicepになりました。次はその抜粋で、先頭の param prefix・param subnetCount の宣言と出力、別ファイルの nested_inner.bicep は省略しています。
var vnetName = '${prefix}-vnet'
resource vnet 'Microsoft.Network/virtualNetworks@2023-09-01' = {
name: vnetName
location: resourceGroup().location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
for j in range(0, subnetCount): {
name: 'snet-${j}'
properties: {
addressPrefix: '10.0.${j}.0/24'
}
}
]
}
}
module inner './nested_inner.bicep' = {
name: 'inner'
params: {
vnetId: vnet.id
}
}
concat() は文字列補間に、プロパティのcopyは for ... in range() に、dependsOn はシンボル参照に置き換わりました。入れ子テンプレートは nested_inner.bicep という別ファイルとして書き出され、moduleで呼ばれます。
同じ入れ子テンプレートを外側スコープ(outer)にして変換すると、入れ子の中で使っていた外側の式が、resourceId_Microsoft_Network_virtualNetworks_variables_vnetName という機械的な名前のパラメーターとして持ち上げられました。このBicepはそのままビルドできましたが、読み手に意味が伝わらないため、移行フェーズ3で付け直す対象です。
Bicep由来のJSONを戻したときに崩れる箇所
前章の89行のJSONをdecompileで戻すと、書き方が元のBicepと変わりました(比較したのは変換結果のファイルで、デプロイ結果は比べていません)。Learnのdecompileの説明も「decompileとbuildの結果は機能的に同等だが、往復すると元と異なる構文になりうる」としています。
- シンボル名が変わる:
saはstorage、blobはstorageName_defaultになりました。 - ループの子リソースが
parentを失う:名前が'${storageName}/default/${item}'の連結に戻り、dependsOnが明示されました。 - 出力が
reference()のまま残る:reference(storage.id, '2024-01-01')となり、linterがuse-resource-symbol-referenceの警告を出しました。
Bicepの元ファイルを失い、生成JSONしか残っていない状態から戻すと、上の3点(シンボル名・parent・reference())を手で直す作業が残ります。リポジトリには .bicep を正として置き、JSONはビルドの成果物として扱ってください。
よくある質問
ARMテンプレートは今後使えなくなりますか?
BicepはデプロイのときにARM JSONへ変換されるため、Azure Resource Managerが受け取る形式はJSONのままです。Microsoftは新しく書くテンプレートにはBicepを勧めていますが、既存のJSONはそのままデプロイでき、Bicepからmoduleとして呼ぶこともできます。
BicepでできてARMテンプレートでできないことはありますか?
デプロイできるリソースと設定は同じです。違いは記述側の機能で、モジュールレジストリ(br:)からの参照、.bicepparam 形式のパラメーターファイル、loadTextContent() などでビルド時にファイルを埋め込む機能はBicepだけにあります。
JSONのテンプレートとBicepを同じリポジトリで併用できますか?
できます。Bicepの module にJSONファイルのパスを指定すれば、パラメーターを渡して出力値を受け取れます。移行は新しく書くリソースからBicepにし、既存のJSONは置き換えの準備ができたものから順に移せます。
decompileしたBicepをそのままデプロイしてよいですか?
推奨されていません。Learnの移行ガイドは、変換結果を参照用の下書きとして扱い、新しいファイルへリソースを1つずつ写すよう求めています。シンボル名やパラメーター名が機械的に付くうえ、decompile自体が「対応関係は保証されない」と警告を出します。
VS CodeでJSONをBicepに変換できますか?
できます。Bicep拡張機能の「Decompile into Bicep」コマンドでファイル単位に変換でき、「Paste JSON as Bicep」ではJSONを貼り付けると自動でdecompileされます。中身はCLIの bicep decompile と同じです。