[{"data":1,"prerenderedAt":2039},["ShallowReactive",2],{"post:\u002F2026\u002F04\u002F15\u002Fazure-landing-zone-design-for-a-mid-size-company-three-years-in\u002F":3},{"post":4,"newer":1988,"older":1998,"related":2008,"series":2037},{"id":5,"title":6,"body":7,"canonical":1970,"categories":1971,"date":1974,"description":1975,"extension":1976,"featured":1977,"hero":1978,"image":1978,"meta":1979,"navigation":134,"path":1980,"readingTime":150,"seo":1981,"series":1978,"seriesOrder":1978,"sites":1982,"source":1978,"stem":1983,"tags":1984,"updated":1978,"url":1986,"__hash__":1987},"blog\u002Fblog\u002F2026\u002F04\u002F15\u002Fazure-landing-zone-design-for-a-mid-size-company-three-years-in.md","Azure: Landing Zone Design for a Mid-Size Company, Three Years In",{"type":8,"value":9,"toc":1962},"minimark",[10,14,19,61,68,89,100,602,605,915,918,922,925,944,947,1015,1410,1413,1420,1834,1838,1841,1856,1860,1872,1876,1879,1883,1958],[11,12,13],"p",{},"I designed my first Azure landing zone with the Cloud Adoption Framework diagrams open in one window and a blank management group tree in the other, trying to guess how much structure a company our size actually needed. Three years and a few painful lessons later, here is what I would tell that version of myself, along with the code I would now start from instead of the portal.",[15,16,18],"h2",{"id":17},"start-with-fewer-management-groups-than-the-diagram-shows","Start With Fewer Management Groups Than the Diagram Shows",[11,20,21,22,26,27,30,31,30,34,37,38,41,42,26,45,30,48,37,51,41,54,37,57,60],{},"The current Azure landing zone reference hierarchy is bigger than most people remember. Under the tenant root it puts an intermediate root with your company prefix, then ",[23,24,25],"code",{},"Platform"," (with ",[23,28,29],{},"Security",", ",[23,32,33],{},"Management",[23,35,36],{},"Connectivity"," and ",[23,39,40],{},"Identity"," children), ",[23,43,44],{},"Landing zones",[23,46,47],{},"Corp",[23,49,50],{},"Online",[23,52,53],{},"Local",[23,55,56],{},"Sandboxes",[23,58,59],{},"Decommissioned",". That is a dozen groups before a single workload lands. I built nearly all of it on day one for a company with maybe fifteen subscriptions, and most of that structure sat empty for two years. A management group with one subscription under it is not governance, it is a folder with extra steps.",[11,62,63,64,67],{},"The CAF guidance itself backs restraint more than the diagram suggests. It recommends keeping the hierarchy \"reasonably flat, ideally with no more than three to four levels\", tells you not to mirror your org chart, and says not to create management groups for production, test and development: separate those by subscription inside the same group. It also recommends pointing new subscriptions at a default management group (a ",[23,65,66],{},"sandbox"," group is the suggested candidate) so nothing lands under the tenant root by accident, and turning on the hierarchy setting that requires authorization to create management groups, because by default any user in the tenant can create one.",[11,69,70,71,73,74,76,77,37,79,81,82,85,86,88],{},"What actually earned its place for us: a ",[23,72,25],{}," group holding the identity, connectivity and management subscriptions directly (no child groups until a platform subscription needed different policy), a ",[23,75,44],{}," group split into ",[23,78,47],{},[23,80,50],{}," for internal versus internet-facing workloads, a ",[23,83,84],{},"Sandbox"," group with policies loose enough that engineers stop provisioning shadow resource groups just to escape the guardrails, and a ",[23,87,59],{}," group to park cancelled subscriptions. Everything else I added later, when a real workload justified it.",[11,90,91,92,95,96,99],{},"Here is that tree as Bicep. Management groups are tenant-level resources, so from a management group deployment each one is declared with ",[23,93,94],{},"scope: tenant()"," and a ",[23,97,98],{},"details.parent.id",", which is the pattern in Microsoft's Bicep management group deployment docs.",[101,102,107],"pre",{"className":103,"code":104,"language":105,"meta":106,"style":106},"language-bicep shiki shiki-themes github-dark","\u002F\u002F mg-hierarchy.bicep: a deliberately small ALZ-style management group tree.\n\u002F\u002F Deploy at the tenant root group, whose ID is the Microsoft Entra tenant ID.\ntargetScope = 'managementGroup'\n\n@description('Short company prefix, used as the intermediate root ID.')\nparam prefix string = 'contoso'\n\n@description('Display name for the intermediate root.')\nparam rootDisplayName string = 'Contoso'\n\nresource intRoot 'Microsoft.Management\u002FmanagementGroups@2023-04-01' = {\n  scope: tenant()\n  name: prefix\n  properties: {\n    displayName: rootDisplayName\n    details: {\n      parent: {\n        id: managementGroup().id\n      }\n    }\n  }\n}\n\nvar topLevel = [\n  {\n    name: '${prefix}-platform'\n    displayName: 'Platform'\n  }\n  {\n    name: '${prefix}-sandbox'\n    displayName: 'Sandbox'\n  }\n  {\n    name: '${prefix}-decommissioned'\n    displayName: 'Decommissioned'\n  }\n]\n\nresource topGroups 'Microsoft.Management\u002FmanagementGroups@2023-04-01' = [for mg in topLevel: {\n  scope: tenant()\n  name: mg.name\n  properties: {\n    displayName: mg.displayName\n    details: {\n      parent: {\n        id: intRoot.id\n      }\n    }\n  }\n}]\n\nresource landingZones 'Microsoft.Management\u002FmanagementGroups@2023-04-01' = {\n  scope: tenant()\n  name: '${prefix}-landingzones'\n  properties: {\n    displayName: 'Landing zones'\n    details: {\n      parent: {\n        id: intRoot.id\n      }\n    }\n  }\n}\n\nvar workloadTypes = [\n  {\n    name: '${prefix}-corp'\n    displayName: 'Corp'\n  }\n  {\n    name: '${prefix}-online'\n    displayName: 'Online'\n  }\n]\n\nresource workloadGroups 'Microsoft.Management\u002FmanagementGroups@2023-04-01' = [for mg in workloadTypes: {\n  scope: tenant()\n  name: mg.name\n  properties: {\n    displayName: mg.displayName\n    details: {\n      parent: {\n        id: landingZones.id\n      }\n    }\n  }\n}]\n\noutput landingZonesId string = landingZones.id\n","bicep","",[23,108,109,117,123,129,136,142,148,153,159,165,170,176,182,188,194,200,206,212,218,224,230,236,242,247,253,259,265,271,276,281,287,293,298,303,309,315,320,326,331,337,342,348,353,359,364,369,375,380,385,390,396,401,407,412,418,423,429,434,439,444,449,454,459,464,469,475,480,486,492,497,502,508,514,519,524,529,535,540,545,550,555,560,565,571,576,581,586,591,596],{"__ignoreMap":106},[110,111,114],"span",{"class":112,"line":113},"line",1,[110,115,116],{},"\u002F\u002F mg-hierarchy.bicep: a deliberately small ALZ-style management group tree.\n",[110,118,120],{"class":112,"line":119},2,[110,121,122],{},"\u002F\u002F Deploy at the tenant root group, whose ID is the Microsoft Entra tenant ID.\n",[110,124,126],{"class":112,"line":125},3,[110,127,128],{},"targetScope = 'managementGroup'\n",[110,130,132],{"class":112,"line":131},4,[110,133,135],{"emptyLinePlaceholder":134},true,"\n",[110,137,139],{"class":112,"line":138},5,[110,140,141],{},"@description('Short company prefix, used as the intermediate root ID.')\n",[110,143,145],{"class":112,"line":144},6,[110,146,147],{},"param prefix string = 'contoso'\n",[110,149,151],{"class":112,"line":150},7,[110,152,135],{"emptyLinePlaceholder":134},[110,154,156],{"class":112,"line":155},8,[110,157,158],{},"@description('Display name for the intermediate root.')\n",[110,160,162],{"class":112,"line":161},9,[110,163,164],{},"param rootDisplayName string = 'Contoso'\n",[110,166,168],{"class":112,"line":167},10,[110,169,135],{"emptyLinePlaceholder":134},[110,171,173],{"class":112,"line":172},11,[110,174,175],{},"resource intRoot 'Microsoft.Management\u002FmanagementGroups@2023-04-01' = {\n",[110,177,179],{"class":112,"line":178},12,[110,180,181],{},"  scope: tenant()\n",[110,183,185],{"class":112,"line":184},13,[110,186,187],{},"  name: prefix\n",[110,189,191],{"class":112,"line":190},14,[110,192,193],{},"  properties: {\n",[110,195,197],{"class":112,"line":196},15,[110,198,199],{},"    displayName: rootDisplayName\n",[110,201,203],{"class":112,"line":202},16,[110,204,205],{},"    details: {\n",[110,207,209],{"class":112,"line":208},17,[110,210,211],{},"      parent: {\n",[110,213,215],{"class":112,"line":214},18,[110,216,217],{},"        id: managementGroup().id\n",[110,219,221],{"class":112,"line":220},19,[110,222,223],{},"      }\n",[110,225,227],{"class":112,"line":226},20,[110,228,229],{},"    }\n",[110,231,233],{"class":112,"line":232},21,[110,234,235],{},"  }\n",[110,237,239],{"class":112,"line":238},22,[110,240,241],{},"}\n",[110,243,245],{"class":112,"line":244},23,[110,246,135],{"emptyLinePlaceholder":134},[110,248,250],{"class":112,"line":249},24,[110,251,252],{},"var topLevel = [\n",[110,254,256],{"class":112,"line":255},25,[110,257,258],{},"  {\n",[110,260,262],{"class":112,"line":261},26,[110,263,264],{},"    name: '${prefix}-platform'\n",[110,266,268],{"class":112,"line":267},27,[110,269,270],{},"    displayName: 'Platform'\n",[110,272,274],{"class":112,"line":273},28,[110,275,235],{},[110,277,279],{"class":112,"line":278},29,[110,280,258],{},[110,282,284],{"class":112,"line":283},30,[110,285,286],{},"    name: '${prefix}-sandbox'\n",[110,288,290],{"class":112,"line":289},31,[110,291,292],{},"    displayName: 'Sandbox'\n",[110,294,296],{"class":112,"line":295},32,[110,297,235],{},[110,299,301],{"class":112,"line":300},33,[110,302,258],{},[110,304,306],{"class":112,"line":305},34,[110,307,308],{},"    name: '${prefix}-decommissioned'\n",[110,310,312],{"class":112,"line":311},35,[110,313,314],{},"    displayName: 'Decommissioned'\n",[110,316,318],{"class":112,"line":317},36,[110,319,235],{},[110,321,323],{"class":112,"line":322},37,[110,324,325],{},"]\n",[110,327,329],{"class":112,"line":328},38,[110,330,135],{"emptyLinePlaceholder":134},[110,332,334],{"class":112,"line":333},39,[110,335,336],{},"resource topGroups 'Microsoft.Management\u002FmanagementGroups@2023-04-01' = [for mg in topLevel: {\n",[110,338,340],{"class":112,"line":339},40,[110,341,181],{},[110,343,345],{"class":112,"line":344},41,[110,346,347],{},"  name: mg.name\n",[110,349,351],{"class":112,"line":350},42,[110,352,193],{},[110,354,356],{"class":112,"line":355},43,[110,357,358],{},"    displayName: mg.displayName\n",[110,360,362],{"class":112,"line":361},44,[110,363,205],{},[110,365,367],{"class":112,"line":366},45,[110,368,211],{},[110,370,372],{"class":112,"line":371},46,[110,373,374],{},"        id: intRoot.id\n",[110,376,378],{"class":112,"line":377},47,[110,379,223],{},[110,381,383],{"class":112,"line":382},48,[110,384,229],{},[110,386,388],{"class":112,"line":387},49,[110,389,235],{},[110,391,393],{"class":112,"line":392},50,[110,394,395],{},"}]\n",[110,397,399],{"class":112,"line":398},51,[110,400,135],{"emptyLinePlaceholder":134},[110,402,404],{"class":112,"line":403},52,[110,405,406],{},"resource landingZones 'Microsoft.Management\u002FmanagementGroups@2023-04-01' = {\n",[110,408,410],{"class":112,"line":409},53,[110,411,181],{},[110,413,415],{"class":112,"line":414},54,[110,416,417],{},"  name: '${prefix}-landingzones'\n",[110,419,421],{"class":112,"line":420},55,[110,422,193],{},[110,424,426],{"class":112,"line":425},56,[110,427,428],{},"    displayName: 'Landing zones'\n",[110,430,432],{"class":112,"line":431},57,[110,433,205],{},[110,435,437],{"class":112,"line":436},58,[110,438,211],{},[110,440,442],{"class":112,"line":441},59,[110,443,374],{},[110,445,447],{"class":112,"line":446},60,[110,448,223],{},[110,450,452],{"class":112,"line":451},61,[110,453,229],{},[110,455,457],{"class":112,"line":456},62,[110,458,235],{},[110,460,462],{"class":112,"line":461},63,[110,463,241],{},[110,465,467],{"class":112,"line":466},64,[110,468,135],{"emptyLinePlaceholder":134},[110,470,472],{"class":112,"line":471},65,[110,473,474],{},"var workloadTypes = [\n",[110,476,478],{"class":112,"line":477},66,[110,479,258],{},[110,481,483],{"class":112,"line":482},67,[110,484,485],{},"    name: '${prefix}-corp'\n",[110,487,489],{"class":112,"line":488},68,[110,490,491],{},"    displayName: 'Corp'\n",[110,493,495],{"class":112,"line":494},69,[110,496,235],{},[110,498,500],{"class":112,"line":499},70,[110,501,258],{},[110,503,505],{"class":112,"line":504},71,[110,506,507],{},"    name: '${prefix}-online'\n",[110,509,511],{"class":112,"line":510},72,[110,512,513],{},"    displayName: 'Online'\n",[110,515,517],{"class":112,"line":516},73,[110,518,235],{},[110,520,522],{"class":112,"line":521},74,[110,523,325],{},[110,525,527],{"class":112,"line":526},75,[110,528,135],{"emptyLinePlaceholder":134},[110,530,532],{"class":112,"line":531},76,[110,533,534],{},"resource workloadGroups 'Microsoft.Management\u002FmanagementGroups@2023-04-01' = [for mg in workloadTypes: {\n",[110,536,538],{"class":112,"line":537},77,[110,539,181],{},[110,541,543],{"class":112,"line":542},78,[110,544,347],{},[110,546,548],{"class":112,"line":547},79,[110,549,193],{},[110,551,553],{"class":112,"line":552},80,[110,554,358],{},[110,556,558],{"class":112,"line":557},81,[110,559,205],{},[110,561,563],{"class":112,"line":562},82,[110,564,211],{},[110,566,568],{"class":112,"line":567},83,[110,569,570],{},"        id: landingZones.id\n",[110,572,574],{"class":112,"line":573},84,[110,575,223],{},[110,577,579],{"class":112,"line":578},85,[110,580,229],{},[110,582,584],{"class":112,"line":583},86,[110,585,235],{},[110,587,589],{"class":112,"line":588},87,[110,590,395],{},[110,592,594],{"class":112,"line":593},88,[110,595,135],{"emptyLinePlaceholder":134},[110,597,599],{"class":112,"line":598},89,[110,600,601],{},"output landingZonesId string = landingZones.id\n",[11,603,604],{},"Deploy it, then set the two hierarchy settings. The root management group's ID is the tenant ID, and only a Global Administrator who has elevated access can grant rights at the root in the first place. Elevation itself only gives you User Access Administrator at root scope, which can't deploy anything, so use it to assign yourself (or the pipeline identity) Owner on the tenant root group first. This is a one-time bootstrap run by someone with that access.",[101,606,610],{"className":607,"code":608,"language":609,"meta":106,"style":106},"language-bash shiki shiki-themes github-dark","#!\u002Fusr\u002Fbin\u002Fenv bash\n# bootstrap-hierarchy.sh: deploy the management group tree and protect the root.\nset -euo pipefail\n\nTENANT_ID=\"\u003Ctenant-id>\"          # the tenant root group has the same ID\nPREFIX=\"contoso\"\nLOCATION=\"eastus\"                # where deployment metadata is stored\n\naz deployment mg create \\\n  --name alz-hierarchy \\\n  --location \"$LOCATION\" \\\n  --management-group-id \"$TENANT_ID\" \\\n  --template-file mg-hierarchy.bicep \\\n  --parameters prefix=\"$PREFIX\"\n\n# New subscriptions land in Sandbox instead of the tenant root,\n# and creating management groups requires write access on the root.\naz account management-group hierarchy-settings create \\\n  --name \"$TENANT_ID\" \\\n  --default-management-group \"\u002Fproviders\u002FMicrosoft.Management\u002FmanagementGroups\u002F${PREFIX}-sandbox\" \\\n  --require-authorization-for-group-creation true\n\n# Place the platform subscriptions directly under Platform.\nfor SUB in \"\u003Cidentity-subscription-id>\" \"\u003Cconnectivity-subscription-id>\" \"\u003Cmanagement-subscription-id>\"; do\n  az account management-group subscription add \\\n    --name \"${PREFIX}-platform\" \\\n    --subscription \"$SUB\"\ndone\n","bash",[23,611,612,618,623,636,640,656,666,679,683,701,711,727,741,751,765,769,774,779,796,808,823,831,835,840,866,883,898,910],{"__ignoreMap":106},[110,613,614],{"class":112,"line":113},[110,615,617],{"class":616},"sAwPA","#!\u002Fusr\u002Fbin\u002Fenv bash\n",[110,619,620],{"class":112,"line":119},[110,621,622],{"class":616},"# bootstrap-hierarchy.sh: deploy the management group tree and protect the root.\n",[110,624,625,629,632],{"class":112,"line":125},[110,626,628],{"class":627},"sDLfK","set",[110,630,631],{"class":627}," -euo",[110,633,635],{"class":634},"sU2Wk"," pipefail\n",[110,637,638],{"class":112,"line":131},[110,639,135],{"emptyLinePlaceholder":134},[110,641,642,646,650,653],{"class":112,"line":138},[110,643,645],{"class":644},"s95oV","TENANT_ID",[110,647,649],{"class":648},"snl16","=",[110,651,652],{"class":634},"\"\u003Ctenant-id>\"",[110,654,655],{"class":616},"          # the tenant root group has the same ID\n",[110,657,658,661,663],{"class":112,"line":144},[110,659,660],{"class":644},"PREFIX",[110,662,649],{"class":648},[110,664,665],{"class":634},"\"contoso\"\n",[110,667,668,671,673,676],{"class":112,"line":150},[110,669,670],{"class":644},"LOCATION",[110,672,649],{"class":648},[110,674,675],{"class":634},"\"eastus\"",[110,677,678],{"class":616},"                # where deployment metadata is stored\n",[110,680,681],{"class":112,"line":155},[110,682,135],{"emptyLinePlaceholder":134},[110,684,685,689,692,695,698],{"class":112,"line":161},[110,686,688],{"class":687},"svObZ","az",[110,690,691],{"class":634}," deployment",[110,693,694],{"class":634}," mg",[110,696,697],{"class":634}," create",[110,699,700],{"class":627}," \\\n",[110,702,703,706,709],{"class":112,"line":167},[110,704,705],{"class":627},"  --name",[110,707,708],{"class":634}," alz-hierarchy",[110,710,700],{"class":627},[110,712,713,716,719,722,725],{"class":112,"line":172},[110,714,715],{"class":627},"  --location",[110,717,718],{"class":634}," \"",[110,720,721],{"class":644},"$LOCATION",[110,723,724],{"class":634},"\"",[110,726,700],{"class":627},[110,728,729,732,734,737,739],{"class":112,"line":178},[110,730,731],{"class":627},"  --management-group-id",[110,733,718],{"class":634},[110,735,736],{"class":644},"$TENANT_ID",[110,738,724],{"class":634},[110,740,700],{"class":627},[110,742,743,746,749],{"class":112,"line":184},[110,744,745],{"class":627},"  --template-file",[110,747,748],{"class":634}," mg-hierarchy.bicep",[110,750,700],{"class":627},[110,752,753,756,759,762],{"class":112,"line":190},[110,754,755],{"class":627},"  --parameters",[110,757,758],{"class":634}," prefix=\"",[110,760,761],{"class":644},"$PREFIX",[110,763,764],{"class":634},"\"\n",[110,766,767],{"class":112,"line":196},[110,768,135],{"emptyLinePlaceholder":134},[110,770,771],{"class":112,"line":202},[110,772,773],{"class":616},"# New subscriptions land in Sandbox instead of the tenant root,\n",[110,775,776],{"class":112,"line":208},[110,777,778],{"class":616},"# and creating management groups requires write access on the root.\n",[110,780,781,783,786,789,792,794],{"class":112,"line":214},[110,782,688],{"class":687},[110,784,785],{"class":634}," account",[110,787,788],{"class":634}," management-group",[110,790,791],{"class":634}," hierarchy-settings",[110,793,697],{"class":634},[110,795,700],{"class":627},[110,797,798,800,802,804,806],{"class":112,"line":220},[110,799,705],{"class":627},[110,801,718],{"class":634},[110,803,736],{"class":644},[110,805,724],{"class":634},[110,807,700],{"class":627},[110,809,810,813,816,818,821],{"class":112,"line":226},[110,811,812],{"class":627},"  --default-management-group",[110,814,815],{"class":634}," \"\u002Fproviders\u002FMicrosoft.Management\u002FmanagementGroups\u002F${",[110,817,660],{"class":644},[110,819,820],{"class":634},"}-sandbox\"",[110,822,700],{"class":627},[110,824,825,828],{"class":112,"line":232},[110,826,827],{"class":627},"  --require-authorization-for-group-creation",[110,829,830],{"class":627}," true\n",[110,832,833],{"class":112,"line":238},[110,834,135],{"emptyLinePlaceholder":134},[110,836,837],{"class":112,"line":244},[110,838,839],{"class":616},"# Place the platform subscriptions directly under Platform.\n",[110,841,842,845,848,851,854,857,860,863],{"class":112,"line":249},[110,843,844],{"class":648},"for",[110,846,847],{"class":644}," SUB ",[110,849,850],{"class":648},"in",[110,852,853],{"class":634}," \"\u003Cidentity-subscription-id>\"",[110,855,856],{"class":634}," \"\u003Cconnectivity-subscription-id>\"",[110,858,859],{"class":634}," \"\u003Cmanagement-subscription-id>\"",[110,861,862],{"class":644},"; ",[110,864,865],{"class":648},"do\n",[110,867,868,871,873,875,878,881],{"class":112,"line":255},[110,869,870],{"class":687},"  az",[110,872,785],{"class":634},[110,874,788],{"class":634},[110,876,877],{"class":634}," subscription",[110,879,880],{"class":634}," add",[110,882,700],{"class":627},[110,884,885,888,891,893,896],{"class":112,"line":261},[110,886,887],{"class":627},"    --name",[110,889,890],{"class":634}," \"${",[110,892,660],{"class":644},[110,894,895],{"class":634},"}-platform\"",[110,897,700],{"class":627},[110,899,900,903,905,908],{"class":112,"line":267},[110,901,902],{"class":627},"    --subscription",[110,904,718],{"class":634},[110,906,907],{"class":644},"$SUB",[110,909,764],{"class":634},[110,911,912],{"class":112,"line":273},[110,913,914],{"class":648},"done\n",[11,916,917],{},"If you want the full reference architecture rather than this trimmed tree, Microsoft's current Bicep path is the ALZ Bicep accelerator built on Azure Verified Modules (AVM), which also uses Azure Deployment Stacks to clean up resources and policies that drop out of the templates. The older ALZ-Bicep modules are now labelled \"classic\". I would still start small and let the accelerator's structure be something you grow into, not something you inherit.",[15,919,921],{"id":920},"policy-assignments-are-a-liability-the-moment-you-stop-reading-them","Policy Assignments Are a Liability the Moment You Stop Reading Them",[11,923,924],{},"Azure Policy is where landing zone design goes to either succeed quietly or fail loudly, and the failure mode is almost always the same: someone assigns a built-in initiative wholesale, it works fine for a year, and then a change to that initiative's underlying policies alters behaviour nobody voted on. I have had a deny effect I never explicitly configured start blocking a deployment because it was bundled two levels deep in an initiative we assigned once and never opened again.",[11,926,927,928,931,932,935,936,939,940,943],{},"This is documented behaviour, not bad luck. Built-in definitions are versioned ",[23,929,930],{},"Major.Minor.Patch",", and Microsoft lists \"adding or moving definitions within an initiative\" and \"minor rule logic changes\" as minor-version changes. An assignment defaults to the latest major version and automatically takes minor and patch updates. You can change that with the assignment's ",[23,933,934],{},"definitionVersion"," property: ",[23,937,938],{},"1.*.*"," follows every minor update, while ",[23,941,942],{},"1.1.*"," pins the minor version and only takes patches (patches are always applied, and are limited to text changes and break-glass fixes).",[11,945,946],{},"The fix that actually worked was boring: prefer individual built-in policies (or custom initiatives you assemble from them) over someone else's bundle, pin the minor version on anything with a deny or modify effect, and review every assignment on a quarterly cadence rather than when something breaks. Policy changes then come from a pull request, not a platform update. These are the three assignments I put at the intermediate root and landing zone groups on day one, all built-in definitions referenced by their fixed IDs:",[948,949,950,966],"table",{},[951,952,953],"thead",{},[954,955,956,960,963],"tr",{},[957,958,959],"th",{},"Built-in policy",[957,961,962],{},"Definition ID",[957,964,965],{},"Why",[967,968,969,983,999],"tbody",{},[954,970,971,975,980],{},[972,973,974],"td",{},"Allowed locations",[972,976,977],{},[23,978,979],{},"e56962a6-4747-49cd-b67b-bf8b01975c4c",[972,981,982],{},"Keeps resources in the regions you have networking and data residency for",[954,984,985,988,993],{},[972,986,987],{},"Require a tag on resource groups",[972,989,990],{},[23,991,992],{},"96670d01-0a4d-4649-9c89-2d3abc0a5025",[972,994,995,996],{},"No resource group without a ",[23,997,998],{},"costCenter",[954,1000,1001,1004,1009],{},[972,1002,1003],{},"Inherit a tag from the resource group if missing",[972,1005,1006],{},[23,1007,1008],{},"ea3f2387-9b95-492a-a190-fcdc54f7b070",[972,1010,1011,1012,1014],{},"Copies ",[23,1013,998],{}," down to resources (modify effect)",[101,1016,1018],{"className":103,"code":1017,"language":105,"meta":106,"style":106},"\u002F\u002F core-policy.bicep: deploy at the intermediate root management group.\ntargetScope = 'managementGroup'\n\nparam allowedLocations array = [\n  'eastus'\n  'eastus2'\n]\n\nparam costTagName string = 'costCenter'\n\n@description('Region for the managed identity that the modify assignment needs.')\nparam identityLocation string = 'eastus'\n\nvar builtIn = {\n  allowedLocations: tenantResourceId('Microsoft.Authorization\u002FpolicyDefinitions', 'e56962a6-4747-49cd-b67b-bf8b01975c4c')\n  requireRgTag: tenantResourceId('Microsoft.Authorization\u002FpolicyDefinitions', '96670d01-0a4d-4649-9c89-2d3abc0a5025')\n  inheritRgTag: tenantResourceId('Microsoft.Authorization\u002FpolicyDefinitions', 'ea3f2387-9b95-492a-a190-fcdc54f7b070')\n}\n\n\u002F\u002F Contributor, the role the inherit-tag definition declares in roleDefinitionIds.\nvar contributorRoleId = 'b24988ac-6180-42a0-ab88-20f7382dd24c'\n\n\u002F\u002F Management group scope limits assignment names to 24 characters.\nresource locations 'Microsoft.Authorization\u002FpolicyAssignments@2025-03-01' = {\n  name: 'allowed-locations'\n  properties: {\n    displayName: 'Allowed locations'\n    policyDefinitionId: builtIn.allowedLocations\n    definitionVersion: '1.1.*'\n    parameters: {\n      listOfAllowedLocations: {\n        value: allowedLocations\n      }\n    }\n    nonComplianceMessages: [\n      {\n        message: 'Deploy only to approved regions. Request an exemption from the platform team if you need another.'\n      }\n    ]\n  }\n}\n\nresource rgTag 'Microsoft.Authorization\u002FpolicyAssignments@2025-03-01' = {\n  name: 'require-rg-costcenter'\n  properties: {\n    displayName: 'Require ${costTagName} on resource groups'\n    policyDefinitionId: builtIn.requireRgTag\n    parameters: {\n      tagName: {\n        value: costTagName\n      }\n    }\n    nonComplianceMessages: [\n      {\n        message: 'Every resource group needs a ${costTagName} tag.'\n      }\n    ]\n  }\n}\n\nresource inheritTag 'Microsoft.Authorization\u002FpolicyAssignments@2025-03-01' = {\n  name: 'inherit-rg-costcenter'\n  location: identityLocation\n  identity: {\n    type: 'SystemAssigned'\n  }\n  properties: {\n    displayName: 'Inherit ${costTagName} from the resource group'\n    policyDefinitionId: builtIn.inheritRgTag\n    parameters: {\n      tagName: {\n        value: costTagName\n      }\n    }\n  }\n}\n\n\u002F\u002F The modify effect needs its identity to hold the declared role at the assignment scope.\nresource inheritTagRole 'Microsoft.Authorization\u002FroleAssignments@2022-04-01' = {\n  name: guid(managementGroup().id, 'inherit-rg-costcenter', contributorRoleId)\n  properties: {\n    roleDefinitionId: tenantResourceId('Microsoft.Authorization\u002FroleDefinitions', contributorRoleId)\n    principalId: inheritTag.identity.principalId\n    principalType: 'ServicePrincipal'\n  }\n}\n",[23,1019,1020,1025,1029,1033,1038,1043,1048,1052,1056,1061,1065,1070,1075,1079,1084,1089,1094,1099,1103,1107,1112,1117,1121,1126,1131,1136,1140,1145,1150,1155,1160,1165,1170,1174,1178,1183,1188,1193,1197,1202,1206,1210,1214,1219,1224,1228,1233,1238,1242,1247,1252,1256,1260,1264,1268,1273,1277,1281,1285,1289,1293,1298,1303,1308,1313,1318,1322,1326,1331,1336,1340,1344,1348,1352,1356,1360,1364,1368,1373,1378,1383,1387,1392,1397,1402,1406],{"__ignoreMap":106},[110,1021,1022],{"class":112,"line":113},[110,1023,1024],{},"\u002F\u002F core-policy.bicep: deploy at the intermediate root management group.\n",[110,1026,1027],{"class":112,"line":119},[110,1028,128],{},[110,1030,1031],{"class":112,"line":125},[110,1032,135],{"emptyLinePlaceholder":134},[110,1034,1035],{"class":112,"line":131},[110,1036,1037],{},"param allowedLocations array = [\n",[110,1039,1040],{"class":112,"line":138},[110,1041,1042],{},"  'eastus'\n",[110,1044,1045],{"class":112,"line":144},[110,1046,1047],{},"  'eastus2'\n",[110,1049,1050],{"class":112,"line":150},[110,1051,325],{},[110,1053,1054],{"class":112,"line":155},[110,1055,135],{"emptyLinePlaceholder":134},[110,1057,1058],{"class":112,"line":161},[110,1059,1060],{},"param costTagName string = 'costCenter'\n",[110,1062,1063],{"class":112,"line":167},[110,1064,135],{"emptyLinePlaceholder":134},[110,1066,1067],{"class":112,"line":172},[110,1068,1069],{},"@description('Region for the managed identity that the modify assignment needs.')\n",[110,1071,1072],{"class":112,"line":178},[110,1073,1074],{},"param identityLocation string = 'eastus'\n",[110,1076,1077],{"class":112,"line":184},[110,1078,135],{"emptyLinePlaceholder":134},[110,1080,1081],{"class":112,"line":190},[110,1082,1083],{},"var builtIn = {\n",[110,1085,1086],{"class":112,"line":196},[110,1087,1088],{},"  allowedLocations: tenantResourceId('Microsoft.Authorization\u002FpolicyDefinitions', 'e56962a6-4747-49cd-b67b-bf8b01975c4c')\n",[110,1090,1091],{"class":112,"line":202},[110,1092,1093],{},"  requireRgTag: tenantResourceId('Microsoft.Authorization\u002FpolicyDefinitions', '96670d01-0a4d-4649-9c89-2d3abc0a5025')\n",[110,1095,1096],{"class":112,"line":208},[110,1097,1098],{},"  inheritRgTag: tenantResourceId('Microsoft.Authorization\u002FpolicyDefinitions', 'ea3f2387-9b95-492a-a190-fcdc54f7b070')\n",[110,1100,1101],{"class":112,"line":214},[110,1102,241],{},[110,1104,1105],{"class":112,"line":220},[110,1106,135],{"emptyLinePlaceholder":134},[110,1108,1109],{"class":112,"line":226},[110,1110,1111],{},"\u002F\u002F Contributor, the role the inherit-tag definition declares in roleDefinitionIds.\n",[110,1113,1114],{"class":112,"line":232},[110,1115,1116],{},"var contributorRoleId = 'b24988ac-6180-42a0-ab88-20f7382dd24c'\n",[110,1118,1119],{"class":112,"line":238},[110,1120,135],{"emptyLinePlaceholder":134},[110,1122,1123],{"class":112,"line":244},[110,1124,1125],{},"\u002F\u002F Management group scope limits assignment names to 24 characters.\n",[110,1127,1128],{"class":112,"line":249},[110,1129,1130],{},"resource locations 'Microsoft.Authorization\u002FpolicyAssignments@2025-03-01' = {\n",[110,1132,1133],{"class":112,"line":255},[110,1134,1135],{},"  name: 'allowed-locations'\n",[110,1137,1138],{"class":112,"line":261},[110,1139,193],{},[110,1141,1142],{"class":112,"line":267},[110,1143,1144],{},"    displayName: 'Allowed locations'\n",[110,1146,1147],{"class":112,"line":273},[110,1148,1149],{},"    policyDefinitionId: builtIn.allowedLocations\n",[110,1151,1152],{"class":112,"line":278},[110,1153,1154],{},"    definitionVersion: '1.1.*'\n",[110,1156,1157],{"class":112,"line":283},[110,1158,1159],{},"    parameters: {\n",[110,1161,1162],{"class":112,"line":289},[110,1163,1164],{},"      listOfAllowedLocations: {\n",[110,1166,1167],{"class":112,"line":295},[110,1168,1169],{},"        value: allowedLocations\n",[110,1171,1172],{"class":112,"line":300},[110,1173,223],{},[110,1175,1176],{"class":112,"line":305},[110,1177,229],{},[110,1179,1180],{"class":112,"line":311},[110,1181,1182],{},"    nonComplianceMessages: [\n",[110,1184,1185],{"class":112,"line":317},[110,1186,1187],{},"      {\n",[110,1189,1190],{"class":112,"line":322},[110,1191,1192],{},"        message: 'Deploy only to approved regions. Request an exemption from the platform team if you need another.'\n",[110,1194,1195],{"class":112,"line":328},[110,1196,223],{},[110,1198,1199],{"class":112,"line":333},[110,1200,1201],{},"    ]\n",[110,1203,1204],{"class":112,"line":339},[110,1205,235],{},[110,1207,1208],{"class":112,"line":344},[110,1209,241],{},[110,1211,1212],{"class":112,"line":350},[110,1213,135],{"emptyLinePlaceholder":134},[110,1215,1216],{"class":112,"line":355},[110,1217,1218],{},"resource rgTag 'Microsoft.Authorization\u002FpolicyAssignments@2025-03-01' = {\n",[110,1220,1221],{"class":112,"line":361},[110,1222,1223],{},"  name: 'require-rg-costcenter'\n",[110,1225,1226],{"class":112,"line":366},[110,1227,193],{},[110,1229,1230],{"class":112,"line":371},[110,1231,1232],{},"    displayName: 'Require ${costTagName} on resource groups'\n",[110,1234,1235],{"class":112,"line":377},[110,1236,1237],{},"    policyDefinitionId: builtIn.requireRgTag\n",[110,1239,1240],{"class":112,"line":382},[110,1241,1159],{},[110,1243,1244],{"class":112,"line":387},[110,1245,1246],{},"      tagName: {\n",[110,1248,1249],{"class":112,"line":392},[110,1250,1251],{},"        value: costTagName\n",[110,1253,1254],{"class":112,"line":398},[110,1255,223],{},[110,1257,1258],{"class":112,"line":403},[110,1259,229],{},[110,1261,1262],{"class":112,"line":409},[110,1263,1182],{},[110,1265,1266],{"class":112,"line":414},[110,1267,1187],{},[110,1269,1270],{"class":112,"line":420},[110,1271,1272],{},"        message: 'Every resource group needs a ${costTagName} tag.'\n",[110,1274,1275],{"class":112,"line":425},[110,1276,223],{},[110,1278,1279],{"class":112,"line":431},[110,1280,1201],{},[110,1282,1283],{"class":112,"line":436},[110,1284,235],{},[110,1286,1287],{"class":112,"line":441},[110,1288,241],{},[110,1290,1291],{"class":112,"line":446},[110,1292,135],{"emptyLinePlaceholder":134},[110,1294,1295],{"class":112,"line":451},[110,1296,1297],{},"resource inheritTag 'Microsoft.Authorization\u002FpolicyAssignments@2025-03-01' = {\n",[110,1299,1300],{"class":112,"line":456},[110,1301,1302],{},"  name: 'inherit-rg-costcenter'\n",[110,1304,1305],{"class":112,"line":461},[110,1306,1307],{},"  location: identityLocation\n",[110,1309,1310],{"class":112,"line":466},[110,1311,1312],{},"  identity: {\n",[110,1314,1315],{"class":112,"line":471},[110,1316,1317],{},"    type: 'SystemAssigned'\n",[110,1319,1320],{"class":112,"line":477},[110,1321,235],{},[110,1323,1324],{"class":112,"line":482},[110,1325,193],{},[110,1327,1328],{"class":112,"line":488},[110,1329,1330],{},"    displayName: 'Inherit ${costTagName} from the resource group'\n",[110,1332,1333],{"class":112,"line":494},[110,1334,1335],{},"    policyDefinitionId: builtIn.inheritRgTag\n",[110,1337,1338],{"class":112,"line":499},[110,1339,1159],{},[110,1341,1342],{"class":112,"line":504},[110,1343,1246],{},[110,1345,1346],{"class":112,"line":510},[110,1347,1251],{},[110,1349,1350],{"class":112,"line":516},[110,1351,223],{},[110,1353,1354],{"class":112,"line":521},[110,1355,229],{},[110,1357,1358],{"class":112,"line":526},[110,1359,235],{},[110,1361,1362],{"class":112,"line":531},[110,1363,241],{},[110,1365,1366],{"class":112,"line":537},[110,1367,135],{"emptyLinePlaceholder":134},[110,1369,1370],{"class":112,"line":542},[110,1371,1372],{},"\u002F\u002F The modify effect needs its identity to hold the declared role at the assignment scope.\n",[110,1374,1375],{"class":112,"line":547},[110,1376,1377],{},"resource inheritTagRole 'Microsoft.Authorization\u002FroleAssignments@2022-04-01' = {\n",[110,1379,1380],{"class":112,"line":552},[110,1381,1382],{},"  name: guid(managementGroup().id, 'inherit-rg-costcenter', contributorRoleId)\n",[110,1384,1385],{"class":112,"line":557},[110,1386,193],{},[110,1388,1389],{"class":112,"line":562},[110,1390,1391],{},"    roleDefinitionId: tenantResourceId('Microsoft.Authorization\u002FroleDefinitions', contributorRoleId)\n",[110,1393,1394],{"class":112,"line":567},[110,1395,1396],{},"    principalId: inheritTag.identity.principalId\n",[110,1398,1399],{"class":112,"line":573},[110,1400,1401],{},"    principalType: 'ServicePrincipal'\n",[110,1403,1404],{"class":112,"line":578},[110,1405,235],{},[110,1407,1408],{"class":112,"line":583},[110,1409,241],{},[11,1411,1412],{},"Two details bit me here. Policy assignment names at management group scope are limited to 24 characters, which is why the names above are terse. And a modify policy only changes resources as they are created or updated; existing resources are marked non-compliant and need a remediation task, which runs as the assignment's managed identity.",[11,1414,1415,1416,1419],{},"The quarterly review is a script, not a meeting. This one exports every assignment at a management group plus the current content of each built-in initiative it references, so you can commit the output and let ",[23,1417,1418],{},"git diff"," tell you what Microsoft changed since last quarter.",[101,1421,1423],{"className":607,"code":1422,"language":609,"meta":106,"style":106},"#!\u002Fusr\u002Fbin\u002Fenv bash\n# policy-snapshot.sh \u003Cmanagement-group-id> [output-dir]\n# Exports assignments at one management group and the member list of every\n# built-in initiative they reference. Commit the output; diff it next quarter.\nset -euo pipefail\n\nMG=\"${1:?usage: policy-snapshot.sh \u003Cmanagement-group-id> [output-dir]}\"\nOUT=\"${2:-policy-snapshot}\"\nmkdir -p \"$OUT\u002Fassignments\" \"$OUT\u002Finitiatives\"\n\naz policy assignment list \\\n  --management-group \"$MG\" \\\n  --filter \"atScope()\" \\\n  --query \"[].{name:name, definition:policyDefinitionId, version:definitionVersion, enforcement:enforcementMode}\" \\\n  --output json > \"$OUT\u002Fassignments\u002F$MG.json\"\n\naz policy assignment list \\\n  --management-group \"$MG\" \\\n  --filter \"atScope()\" \\\n  --query \"[].policyDefinitionId\" \\\n  --output tsv |\nwhile read -r defId; do\n  case \"$defId\" in\n    \u002Fproviders\u002FMicrosoft.Authorization\u002FpolicySetDefinitions\u002F*)\n      name=\"${defId##*\u002F}\"\n      az policy set-definition show \\\n        --name \"$name\" \\\n        --query \"{displayName:displayName, version:version, policies:policyDefinitions[].{ref:policyDefinitionReferenceId, id:policyDefinitionId, version:definitionVersion}}\" \\\n        --output json > \"$OUT\u002Finitiatives\u002F$name.json\"\n      ;;\n  esac\ndone\n\necho \"Snapshot written to $OUT. Review with: git diff --stat -- $OUT\"\n",[23,1424,1425,1429,1434,1439,1444,1452,1456,1500,1522,1545,1549,1564,1578,1588,1598,1621,1625,1637,1649,1657,1666,1676,1694,1709,1721,1740,1755,1769,1779,1799,1804,1809,1813,1817],{"__ignoreMap":106},[110,1426,1427],{"class":112,"line":113},[110,1428,617],{"class":616},[110,1430,1431],{"class":112,"line":119},[110,1432,1433],{"class":616},"# policy-snapshot.sh \u003Cmanagement-group-id> [output-dir]\n",[110,1435,1436],{"class":112,"line":125},[110,1437,1438],{"class":616},"# Exports assignments at one management group and the member list of every\n",[110,1440,1441],{"class":112,"line":131},[110,1442,1443],{"class":616},"# built-in initiative they reference. Commit the output; diff it next quarter.\n",[110,1445,1446,1448,1450],{"class":112,"line":138},[110,1447,628],{"class":627},[110,1449,631],{"class":627},[110,1451,635],{"class":634},[110,1453,1454],{"class":112,"line":144},[110,1455,135],{"emptyLinePlaceholder":134},[110,1457,1458,1461,1463,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498],{"class":112,"line":150},[110,1459,1460],{"class":644},"MG",[110,1462,649],{"class":648},[110,1464,724],{"class":634},[110,1466,1467],{"class":627},"${1",[110,1469,1470],{"class":648},":?",[110,1472,1473],{"class":644},"usage",[110,1475,1476],{"class":648},":",[110,1478,1479],{"class":644}," policy-snapshot",[110,1481,1482],{"class":634},".",[110,1484,1485],{"class":644},"sh",[110,1487,1488],{"class":634}," \u003C",[110,1490,1491],{"class":644},"management-group-id",[110,1493,1494],{"class":634},"> [output-dir]",[110,1496,1497],{"class":627},"}",[110,1499,764],{"class":634},[110,1501,1502,1505,1507,1509,1512,1515,1518,1520],{"class":112,"line":155},[110,1503,1504],{"class":644},"OUT",[110,1506,649],{"class":648},[110,1508,724],{"class":634},[110,1510,1511],{"class":627},"${2",[110,1513,1514],{"class":648},":-",[110,1516,1517],{"class":644},"policy-snapshot",[110,1519,1497],{"class":627},[110,1521,764],{"class":634},[110,1523,1524,1527,1530,1532,1535,1538,1540,1542],{"class":112,"line":161},[110,1525,1526],{"class":687},"mkdir",[110,1528,1529],{"class":627}," -p",[110,1531,718],{"class":634},[110,1533,1534],{"class":644},"$OUT",[110,1536,1537],{"class":634},"\u002Fassignments\"",[110,1539,718],{"class":634},[110,1541,1534],{"class":644},[110,1543,1544],{"class":634},"\u002Finitiatives\"\n",[110,1546,1547],{"class":112,"line":167},[110,1548,135],{"emptyLinePlaceholder":134},[110,1550,1551,1553,1556,1559,1562],{"class":112,"line":172},[110,1552,688],{"class":687},[110,1554,1555],{"class":634}," policy",[110,1557,1558],{"class":634}," assignment",[110,1560,1561],{"class":634}," list",[110,1563,700],{"class":627},[110,1565,1566,1569,1571,1574,1576],{"class":112,"line":178},[110,1567,1568],{"class":627},"  --management-group",[110,1570,718],{"class":634},[110,1572,1573],{"class":644},"$MG",[110,1575,724],{"class":634},[110,1577,700],{"class":627},[110,1579,1580,1583,1586],{"class":112,"line":184},[110,1581,1582],{"class":627},"  --filter",[110,1584,1585],{"class":634}," \"atScope()\"",[110,1587,700],{"class":627},[110,1589,1590,1593,1596],{"class":112,"line":190},[110,1591,1592],{"class":627},"  --query",[110,1594,1595],{"class":634}," \"[].{name:name, definition:policyDefinitionId, version:definitionVersion, enforcement:enforcementMode}\"",[110,1597,700],{"class":627},[110,1599,1600,1603,1606,1609,1611,1613,1616,1618],{"class":112,"line":196},[110,1601,1602],{"class":627},"  --output",[110,1604,1605],{"class":634}," json",[110,1607,1608],{"class":648}," >",[110,1610,718],{"class":634},[110,1612,1534],{"class":644},[110,1614,1615],{"class":634},"\u002Fassignments\u002F",[110,1617,1573],{"class":644},[110,1619,1620],{"class":634},".json\"\n",[110,1622,1623],{"class":112,"line":202},[110,1624,135],{"emptyLinePlaceholder":134},[110,1626,1627,1629,1631,1633,1635],{"class":112,"line":208},[110,1628,688],{"class":687},[110,1630,1555],{"class":634},[110,1632,1558],{"class":634},[110,1634,1561],{"class":634},[110,1636,700],{"class":627},[110,1638,1639,1641,1643,1645,1647],{"class":112,"line":214},[110,1640,1568],{"class":627},[110,1642,718],{"class":634},[110,1644,1573],{"class":644},[110,1646,724],{"class":634},[110,1648,700],{"class":627},[110,1650,1651,1653,1655],{"class":112,"line":220},[110,1652,1582],{"class":627},[110,1654,1585],{"class":634},[110,1656,700],{"class":627},[110,1658,1659,1661,1664],{"class":112,"line":226},[110,1660,1592],{"class":627},[110,1662,1663],{"class":634}," \"[].policyDefinitionId\"",[110,1665,700],{"class":627},[110,1667,1668,1670,1673],{"class":112,"line":232},[110,1669,1602],{"class":627},[110,1671,1672],{"class":634}," tsv",[110,1674,1675],{"class":648}," |\n",[110,1677,1678,1681,1684,1687,1690,1692],{"class":112,"line":238},[110,1679,1680],{"class":648},"while",[110,1682,1683],{"class":634}," read",[110,1685,1686],{"class":627}," -r",[110,1688,1689],{"class":634}," defId",[110,1691,862],{"class":644},[110,1693,865],{"class":648},[110,1695,1696,1699,1701,1704,1706],{"class":112,"line":244},[110,1697,1698],{"class":648},"  case",[110,1700,718],{"class":634},[110,1702,1703],{"class":644},"$defId",[110,1705,724],{"class":634},[110,1707,1708],{"class":648}," in\n",[110,1710,1711,1715,1718],{"class":112,"line":249},[110,1712,1714],{"class":1713},"sns5M","    \u002Fproviders\u002FMicrosoft.Authorization\u002FpolicySetDefinitions\u002F",[110,1716,1717],{"class":648},"*",[110,1719,1720],{"class":648},")\n",[110,1722,1723,1726,1728,1731,1734,1737],{"class":112,"line":255},[110,1724,1725],{"class":644},"      name",[110,1727,649],{"class":648},[110,1729,1730],{"class":634},"\"${",[110,1732,1733],{"class":644},"defId",[110,1735,1736],{"class":648},"##*\u002F",[110,1738,1739],{"class":634},"}\"\n",[110,1741,1742,1745,1747,1750,1753],{"class":112,"line":261},[110,1743,1744],{"class":687},"      az",[110,1746,1555],{"class":634},[110,1748,1749],{"class":634}," set-definition",[110,1751,1752],{"class":634}," show",[110,1754,700],{"class":627},[110,1756,1757,1760,1762,1765,1767],{"class":112,"line":267},[110,1758,1759],{"class":627},"        --name",[110,1761,718],{"class":634},[110,1763,1764],{"class":644},"$name",[110,1766,724],{"class":634},[110,1768,700],{"class":627},[110,1770,1771,1774,1777],{"class":112,"line":273},[110,1772,1773],{"class":627},"        --query",[110,1775,1776],{"class":634}," \"{displayName:displayName, version:version, policies:policyDefinitions[].{ref:policyDefinitionReferenceId, id:policyDefinitionId, version:definitionVersion}}\"",[110,1778,700],{"class":627},[110,1780,1781,1784,1786,1788,1790,1792,1795,1797],{"class":112,"line":278},[110,1782,1783],{"class":627},"        --output",[110,1785,1605],{"class":634},[110,1787,1608],{"class":648},[110,1789,718],{"class":634},[110,1791,1534],{"class":644},[110,1793,1794],{"class":634},"\u002Finitiatives\u002F",[110,1796,1764],{"class":644},[110,1798,1620],{"class":634},[110,1800,1801],{"class":112,"line":283},[110,1802,1803],{"class":644},"      ;;\n",[110,1805,1806],{"class":112,"line":289},[110,1807,1808],{"class":648},"  esac\n",[110,1810,1811],{"class":112,"line":295},[110,1812,914],{"class":648},[110,1814,1815],{"class":112,"line":300},[110,1816,135],{"emptyLinePlaceholder":134},[110,1818,1819,1822,1825,1827,1830,1832],{"class":112,"line":305},[110,1820,1821],{"class":627},"echo",[110,1823,1824],{"class":634}," \"Snapshot written to ",[110,1826,1534],{"class":644},[110,1828,1829],{"class":634},". Review with: git diff --stat -- ",[110,1831,1534],{"class":644},[110,1833,764],{"class":634},[15,1835,1837],{"id":1836},"subscription-vending-beats-subscription-requests","Subscription Vending Beats Subscription Requests",[11,1839,1840],{},"For the first eighteen months, every new subscription meant a ticket, a manual policy pass, and someone remembering to wire up the right network peering and log forwarding. It worked until it did not scale past the two people who understood the whole checklist. Automating subscription creation as a vending process, a pipeline that runs the same governance steps every time, was the single highest-leverage change I made to the landing zone.",[11,1842,1843,1844,37,1847,1850,1851,1482],{},"CAF now describes exactly this. The vending automation should capture the request (budget, owners, networking, criticality) at intake, place the subscription in the right management group, create the peered virtual network, assign access through Entra groups rather than individuals, tag it for cost reporting, and create a starting budget the application team then adjusts. Microsoft publishes subscription vending modules for Bicep and Terraform (",[23,1845,1846],{},"aka.ms\u002Flz-vending\u002Fbicep",[23,1848,1849],{},"aka.ms\u002Flz-vending\u002Ftf","), and notes that creating subscriptions programmatically needs an EA, MCA or MPA agreement, while everything after creation can be automated regardless. New subscriptions come out identical now, and the checklist lives in code review history instead of one person's memory. For the access side, I also run a periodic check of who holds Owner across every subscription; the script is in ",[1852,1853,1855],"a",{"href":1854},"\u002F2025\u002F11\u002F19\u002Fpowershell-azure-audit-who-has-owner-role-across-all-subscriptions\u002F","Audit Who Has Owner Role Across All Subscriptions",[15,1857,1859],{"id":1858},"cost-management-should-have-been-day-one-not-year-two","Cost Management Should Have Been Day One, Not Year Two",[11,1861,1862,1863,1866,1867,1871],{},"I treated cost management as an operational nicety and bolted it on well after the platform was live, which meant a year of budget alerts configured inconsistently across subscriptions and no shared tagging standard to slice spend by team or environment. Retrofitting tags onto resources that already existed is much harder than enforcing them at the vending stage, where the ",[23,1864,1865],{},"require-rg-costcenter"," assignment above simply blocks the deployment. For the backlog of untagged resources that predate the policy, ",[1852,1868,1870],{"href":1869},"\u002F2025\u002F09\u002F24\u002Fpowershell-azure-tag-every-untagged-resource-in-a-subscription\u002F","Tag Every Untagged Resource in a Subscription"," is the cleanup pass I used.",[15,1873,1875],{"id":1874},"what-i-would-keep-doing","What I Would Keep Doing",[11,1877,1878],{},"The hub-and-spoke network topology, the centralized Log Analytics workspace in the management subscription, and the decision to build identity first rather than as an afterthought all held up under three years of growth. So did keeping application teams' role assignments at subscription or resource group scope, which is also what CAF recommends: management groups are for policy, and standing RBAC at that level is reserved for platform staff through Privileged Identity Management. Landing zone design rewards restraint on scaffolding and discipline on the few controls that actually govern risk. Everything else can be added later, and usually should be.",[15,1880,1882],{"id":1881},"references","References",[1884,1885,1886,1895,1902,1909,1916,1923,1930,1937,1944,1951],"ul",{},[1887,1888,1889],"li",{},[1852,1890,1894],{"href":1891,"rel":1892},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fcloud-adoption-framework\u002Fready\u002Flanding-zone\u002Fdesign-area\u002Fresource-org-management-groups",[1893],"nofollow","Management groups (Cloud Adoption Framework design area)",[1887,1896,1897],{},[1852,1898,1901],{"href":1899,"rel":1900},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fcloud-adoption-framework\u002Fready\u002Flanding-zone\u002Fdesign-area\u002Fsubscription-vending",[1893],"Subscription vending (Cloud Adoption Framework)",[1887,1903,1904],{},[1852,1905,1908],{"href":1906,"rel":1907},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fgovernance\u002Fmanagement-groups\u002Fhow-to\u002Fprotect-resource-hierarchy",[1893],"Protect your resource hierarchy",[1887,1910,1911],{},[1852,1912,1915],{"href":1913,"rel":1914},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fazure-resource-manager\u002Fbicep\u002Fdeploy-to-management-group",[1893],"Use Bicep to deploy resources to a management group",[1887,1917,1918],{},[1852,1919,1922],{"href":1920,"rel":1921},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fgovernance\u002Fpolicy\u002Fconcepts\u002Fdefinition-structure-basics",[1893],"Azure Policy definition structure basics (versioning)",[1887,1924,1925],{},[1852,1926,1929],{"href":1927,"rel":1928},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fgovernance\u002Fpolicy\u002Fconcepts\u002Fassignment-structure",[1893],"Azure Policy assignment structure (definitionVersion)",[1887,1931,1932],{},[1852,1933,1936],{"href":1934,"rel":1935},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fgovernance\u002Fpolicy\u002Fconcepts\u002Feffect-modify",[1893],"Azure Policy modify effect",[1887,1938,1939],{},[1852,1940,1943],{"href":1941,"rel":1942},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fgovernance\u002Fpolicy\u002Fsamples\u002Fbuilt-in-policies",[1893],"Azure Policy built-in policy definitions",[1887,1945,1946],{},[1852,1947,1950],{"href":1948,"rel":1949},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fazure-resource-manager\u002Fmanagement\u002Fresource-name-rules",[1893],"Naming rules for Azure resources (policy assignment name limits)",[1887,1952,1953],{},[1852,1954,1957],{"href":1955,"rel":1956},"https:\u002F\u002Fazure.github.io\u002FAzure-Landing-Zones\u002Fbicep\u002F",[1893],"Azure landing zones Bicep implementation",[1959,1960,1961],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sns5M, html code.shiki .sns5M{--shiki-default:#DBEDFF}",{"title":106,"searchDepth":119,"depth":119,"links":1963},[1964,1965,1966,1967,1968,1969],{"id":17,"depth":119,"text":18},{"id":920,"depth":119,"text":921},{"id":1836,"depth":119,"text":1837},{"id":1858,"depth":119,"text":1859},{"id":1874,"depth":119,"text":1875},{"id":1881,"depth":119,"text":1882},"techcolumnist",[1972,1973],"engineering","strategy","2026-04-15T14:00:00Z","Three years of running an Azure landing zone: a smaller management group tree, pinned policy versions, and subscription vending, with the Bicep and az CLI to build it.","md",false,null,{},"\u002Fblog\u002F2026\u002F04\u002F15\u002Fazure-landing-zone-design-for-a-mid-size-company-three-years-in",{"title":6,"description":1975},[1970],"blog\u002F2026\u002F04\u002F15\u002Fazure-landing-zone-design-for-a-mid-size-company-three-years-in",[1985],"azure","\u002F2026\u002F04\u002F15\u002Fazure-landing-zone-design-for-a-mid-size-company-three-years-in\u002F","aO164qrGD74ja674s3-uxi6jsIJwtZKZaiZOyFWBA4M",{"title":1989,"description":1990,"date":1991,"url":1992,"categories":1993,"tags":1994,"image":1978,"readingTime":155,"canonical":1970,"sites":1997,"series":1978,"seriesOrder":1978},"Azure: What We Got Wrong Moving File Servers to Azure Files","Latency, AD DS Kerberos, and cloud tiering mistakes from moving file servers to Azure Files, with the robocopy migration script and az CLI setup we use now.","2026-04-22T14:00:00Z","\u002F2026\u002F04\u002F22\u002Fazure-what-we-got-wrong-moving-file-servers-to-azure-files\u002F",[1972],[1985,1995,1996],"active-directory","dfs",[1970],{"title":1999,"description":2000,"date":2001,"url":2002,"categories":2003,"tags":2004,"image":1978,"readingTime":155,"canonical":1970,"sites":2007,"series":1978,"seriesOrder":1978},"Intune: Autopilot Provisioning Failures and How to Actually Debug Them","Debugging Windows Autopilot failures from the evidence: documented error codes, event IDs, ESP tracking and timeouts, log collection, and triage scripts for device and Graph.","2026-04-08T14:00:00Z","\u002F2026\u002F04\u002F08\u002Fintune-autopilot-provisioning-failures-and-how-to-actually-debug-them\u002F",[1972],[2005,2006],"intune","windows",[1970],[2009,2017,2028],{"title":2010,"description":2011,"date":2012,"url":2013,"categories":2014,"tags":2015,"image":1978,"readingTime":150,"canonical":1970,"sites":2016,"series":1978,"seriesOrder":1978},"Azure: Cost Governance Without Killing Developer Velocity","Azure cost governance that catches waste without approval gates: built-in policy guardrails, budget alerts as code, and scheduled cleanup, with the scripts.","2026-08-12T14:00:00Z","\u002F2026\u002F08\u002F12\u002Fazure-cost-governance-without-killing-developer-velocity\u002F",[1972,1973],[1985],[1970],{"title":2018,"description":2019,"date":2020,"url":2021,"categories":2022,"tags":2023,"image":1978,"readingTime":138,"canonical":1970,"sites":2027,"series":1978,"seriesOrder":1978},"Hyper-V to Talos: Rethinking the Virtualization Stack","Why moving workloads off Hyper-V VMs onto a Talos Kubernetes cluster changed the whole stack, with an inventory script and a VM-to-manifest example.","2026-09-16T14:00:00Z","\u002F2026\u002F09\u002F16\u002Fhyper-v-to-talos-rethinking-the-virtualization-stack\u002F",[1972,1973],[2024,2025,2026],"hyper-v","talos","kubernetes",[1970],{"title":2029,"description":2030,"date":2031,"url":2032,"categories":2033,"tags":2034,"image":1978,"readingTime":138,"canonical":1970,"sites":2036,"series":1978,"seriesOrder":1978},"Windows Management: A Discovery Inventory Nobody Maintains by Hand","Why a self-refreshing Windows inventory built from AD and CIM discovery outlasts a spreadsheet, with the sweep script, staleness report and schedule.","2026-09-02T14:00:00Z","\u002F2026\u002F09\u002F02\u002Fwindows-management-building-a-discovery-inventory-nobody-has-to-maintain-by-hand\u002F",[1972,1973],[2035,2006,1995],"discovery",[1970],{"doc":1978,"posts":2038},[],1790052513904]