Skip to content

ADR-0006: Single-Root Composition

Status: Implemented | Date: 2026-04-17

Bicep requires two separate templates because a template can only have one targetScope:

  • deploy-mg.bicep (targetScope = 'managementGroup') — MG creation + 33 policy assignments
  • main.bicep (targetScope = 'subscription') — everything else

Terraform has no such restriction. A single root can declare azurerm_management_group, azurerm_management_group_policy_assignment, and azurerm_resource_group resources in the same configuration. The question: keep the two-step split for symmetry, or collapse?

Collapse into a single root with 17 child modules. Management group, policy assignments, and subscription-scope resources all live in one Terraform configuration.

  • Root-level import block: Targets azurerm_management_group.smb_rf so that terraform apply succeeds regardless of whether the MG pre-exists (created by Bicep or a prior run)
  • lifecycle.ignore_changes = [subscription_ids]: Prevents drift when subscriptions are manually moved
  • Single state file: smb-ready-foundation.tfstate in the bootstrapped Azure Storage backend
  • Module orchestration: main.tf composes 17 module calls with explicit depends_on for ordering

Positive: Single terraform apply instead of two-step orchestration, simpler CI/CD pipeline, one state file to manage, no hook-based scope handoff.

Negative: Larger blast radius per apply (MG changes and resource changes in one operation), slightly more complex main.tf with cross-scope dependencies.